提交 2ac9f371 编写于 作者: Q Quleaf

update to v2.2.2

上级 9a5ad8a6
...@@ -33,7 +33,7 @@ English | [简体中文](README_CN.md) ...@@ -33,7 +33,7 @@ English | [简体中文](README_CN.md)
</a> </a>
<!-- PyPI --> <!-- PyPI -->
<a href="https://pypi.org/project/paddle-quantum/"> <a href="https://pypi.org/project/paddle-quantum/">
<img src="https://img.shields.io/badge/pypi-v2.2.1-orange.svg?style=flat-square&logo=pypi"/> <img src="https://img.shields.io/badge/pypi-v2.2.2-orange.svg?style=flat-square&logo=pypi"/>
</a> </a>
<!-- Python --> <!-- Python -->
<a href="https://www.python.org/"> <a href="https://www.python.org/">
...@@ -152,6 +152,9 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria ...@@ -152,6 +152,9 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria
9. [Simulate the Spin Dynamics on a Heisenberg Chain](./tutorials/quantum_simulation/SimulateHeisenberg_EN.ipynb) 9. [Simulate the Spin Dynamics on a Heisenberg Chain](./tutorials/quantum_simulation/SimulateHeisenberg_EN.ipynb)
10. [Distributed Variational Quantum Eigensolver Based on Schmidt Decomposition](./tutorials/quantum_simulation/DistributedVQE_EN.ipynb) 10. [Distributed Variational Quantum Eigensolver Based on Schmidt Decomposition](./tutorials/quantum_simulation/DistributedVQE_EN.ipynb)
11. [Quantum Signal Processing and Quantum Singular Value Transformation](./tutorials/quantum_simulation/QSP_and_QSVT_EN.ipynb) 11. [Quantum Signal Processing and Quantum Singular Value Transformation](./tutorials/quantum_simulation/QSP_and_QSVT_EN.ipynb)
12. [Hamiltonian Simulation with qDRIFT](./tutorials/quantum_simulation/QDRIFT_EN.ipynb)
13. [Quantum Phase Processing](./tutorials/quantum_simulation/QPP_EN.ipynb)
14. [Variational Quantum Metrology](./tutorials/quantum_simulation/VariationalQM_EN.ipynb)
- [Machine Learning](./tutorials/machine_learning) - [Machine Learning](./tutorials/machine_learning)
1. [Encoding Classical Data into Quantum States](./tutorials/machine_learning/DataEncoding_EN.ipynb) 1. [Encoding Classical Data into Quantum States](./tutorials/machine_learning/DataEncoding_EN.ipynb)
...@@ -163,6 +166,7 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria ...@@ -163,6 +166,7 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria
7. [Variational Quantum Singular Value Decomposition (VQSVD)](./tutorials/machine_learning/VQSVD_EN.ipynb) 7. [Variational Quantum Singular Value Decomposition (VQSVD)](./tutorials/machine_learning/VQSVD_EN.ipynb)
8. [Data Encoding Analysis](./tutorials/machine_learning/EncodingAnalysis_EN.ipynb) 8. [Data Encoding Analysis](./tutorials/machine_learning/EncodingAnalysis_EN.ipynb)
9. [Quantum Neural Network Approximating Functions](./tutorials/machine_learning/QApproximating_EN.ipynb) 9. [Quantum Neural Network Approximating Functions](./tutorials/machine_learning/QApproximating_EN.ipynb)
10. [Variational quantum amplitude estimation](./tutorials/machine_learning/VQAE_EN.ipynb)
- [Combinatorial Optimization](./tutorials/combinatorial_optimization) - [Combinatorial Optimization](./tutorials/combinatorial_optimization)
1. [Quantum Approximation Optimization Algorithm (QAOA)](./tutorials/combinatorial_optimization/QAOA_EN.ipynb) 1. [Quantum Approximation Optimization Algorithm (QAOA)](./tutorials/combinatorial_optimization/QAOA_EN.ipynb)
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
</a> </a>
<!-- PyPI --> <!-- PyPI -->
<a href="https://pypi.org/project/paddle-quantum/"> <a href="https://pypi.org/project/paddle-quantum/">
<img src="https://img.shields.io/badge/pypi-v2.2.1-orange.svg?style=flat-square&logo=pypi"/> <img src="https://img.shields.io/badge/pypi-v2.2.2-orange.svg?style=flat-square&logo=pypi"/>
</a> </a>
<!-- Python --> <!-- Python -->
<a href="https://www.python.org/"> <a href="https://www.python.org/">
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
### 安装 Paddle Quantum ### 安装 Paddle Quantum
我们推荐通过 `pip` 完成安装, 我们推荐通过 `pip` 完成安装
```bash ```bash
pip install paddle-quantum pip install paddle-quantum
...@@ -161,6 +161,9 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为 ...@@ -161,6 +161,9 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为
9. [模拟一维海森堡链的自旋动力学](./tutorials/quantum_simulation/SimulateHeisenberg_CN.ipynb) 9. [模拟一维海森堡链的自旋动力学](./tutorials/quantum_simulation/SimulateHeisenberg_CN.ipynb)
10. [基于施密特分解的分布式变分量子本征求解器](./tutorials/quantum_simulation/DistributedVQE_CN.ipynb) 10. [基于施密特分解的分布式变分量子本征求解器](./tutorials/quantum_simulation/DistributedVQE_CN.ipynb)
11. [量子信号处理与量子奇异值变换](./tutorials/quantum_simulation/QSP_and_QSVT_CN.ipynb) 11. [量子信号处理与量子奇异值变换](./tutorials/quantum_simulation/QSP_and_QSVT_CN.ipynb)
12. [利用 qDRIFT 模拟时间演化](./tutorials/quantum_simulation/QDRIFT_CN.ipynb)
13. [量子相位处理](./tutorials/quantum_simulation/QPP_CN.ipynb)
14. [变分量子精密测量](./tutorials/quantum_simulation/VariationalQM_CN.ipynb)
- [机器学习](./tutorials/machine_learning) - [机器学习](./tutorials/machine_learning)
...@@ -173,6 +176,7 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为 ...@@ -173,6 +176,7 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为
7. [变分量子奇异值分解(VQSVD)](./tutorials/machine_learning/VQSVD_CN.ipynb) 7. [变分量子奇异值分解(VQSVD)](./tutorials/machine_learning/VQSVD_CN.ipynb)
8. [数据编码分析](./tutorials/machine_learning/EncodingAnalysis_CN.ipynb) 8. [数据编码分析](./tutorials/machine_learning/EncodingAnalysis_CN.ipynb)
9. [量子神经网络模拟函数](./tutorials/machine_learning/QApproximating_CN.ipynb) 9. [量子神经网络模拟函数](./tutorials/machine_learning/QApproximating_CN.ipynb)
10. [变分量子振幅估算](./tutorials/machine_learning/VQAE_CN.ipynb)
- [组合优化](./tutorials/combinatorial_optimization) - [组合优化](./tutorials/combinatorial_optimization)
1. [量子近似优化算法(QAOA)](./tutorials/combinatorial_optimization/QAOA_CN.ipynb) 1. [量子近似优化算法(QAOA)](./tutorials/combinatorial_optimization/QAOA_CN.ipynb)
......
...@@ -19,7 +19,9 @@ ...@@ -19,7 +19,9 @@
paddle_quantum.hamiltonian paddle_quantum.hamiltonian
paddle_quantum.linalg paddle_quantum.linalg
paddle_quantum.qinfo paddle_quantum.qinfo
paddle_quantum.qml
paddle_quantum.shadow paddle_quantum.shadow
paddle_quantum.trotter paddle_quantum.trotter
paddle_quantum.visual paddle_quantum.visual
paddle_quantum.qsvt paddle_quantum.qsvt
\ No newline at end of file paddle_quantum.qpp
...@@ -13,3 +13,4 @@ paddle\_quantum.gate.functional ...@@ -13,3 +13,4 @@ paddle\_quantum.gate.functional
paddle_quantum.gate.functional.base paddle_quantum.gate.functional.base
paddle_quantum.gate.functional.multi_qubit_gate paddle_quantum.gate.functional.multi_qubit_gate
paddle_quantum.gate.functional.single_qubit_gate paddle_quantum.gate.functional.single_qubit_gate
paddle_quantum.gate.functional.visual
\ No newline at end of file
paddle\_quantum.gate.functional.base
===========================================
.. automodule:: paddle_quantum.gate.functional.visual
:members:
:show-inheritance:
paddle\_quantum.qml
============================
.. automodule:: paddle_quantum.qml
:members:
:undoc-members:
:show-inheritance:
.. rubric:: Submodules
.. toctree::
:maxdepth: 4
paddle_quantum.qml.vsql
paddle\_quantum.qml.vsql
==================================
.. automodule:: paddle_quantum.qml.vsql
:members:
:undoc-members:
:show-inheritance:
paddle\_quantum.qpp.angles
============================
.. automodule:: paddle_quantum.qpp.angles
:members:
:undoc-members:
:show-inheritance:
\ No newline at end of file
paddle\_quantum.qpp.laurent
============================
.. automodule:: paddle_quantum.qpp.laurent
:members:
:undoc-members:
:show-inheritance:
\ No newline at end of file
paddle\_quantum.qpp
============================
.. automodule:: paddle_quantum.qpp
:members:
:undoc-members:
:show-inheritance:
.. rubric:: Submodules
.. toctree::
:maxdepth: 4
paddle_quantum.qpp.angles
paddle_quantum.qpp.laurent
paddle_quantum.qpp.utils
\ No newline at end of file
paddle\_quantum.qpp.qpp.utils
=============================
.. automodule:: paddle_quantum.qpp.utils
:members:
:undoc-members:
:show-inheritance:
\ No newline at end of file
...@@ -19,7 +19,9 @@ ...@@ -19,7 +19,9 @@
paddle_quantum.hamiltonian paddle_quantum.hamiltonian
paddle_quantum.linalg paddle_quantum.linalg
paddle_quantum.qinfo paddle_quantum.qinfo
paddle_quantum.qml
paddle_quantum.shadow paddle_quantum.shadow
paddle_quantum.trotter paddle_quantum.trotter
paddle_quantum.visual paddle_quantum.visual
paddle_quantum.qsvt paddle_quantum.qsvt
paddle_quantum.qpp
...@@ -28,10 +28,6 @@ paddle\_quantum.ansatz.circuit ...@@ -28,10 +28,6 @@ paddle\_quantum.ansatz.circuit
展平后的电路参数梯度。 展平后的电路参数梯度。
.. py:property:: depth()
电路深度
.. py:method:: update_param(theta, idx=None) .. py:method:: update_param(theta, idx=None)
替换单层或所有的电路参数。 替换单层或所有的电路参数。
...@@ -98,6 +94,27 @@ paddle\_quantum.ansatz.circuit ...@@ -98,6 +94,27 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: sdg(qubits_idx='full', num_qubits=None, depth=1)
添加单量子比特 S dagger (逆S)门。
其矩阵形式为:
.. math::
S ^\dagger =
\begin{bmatrix}
1 & 0\ \
0 & -i
\end{bmatrix}
:param qubits_idx: 作用在的量子比特的编号,默认为 ``'full'``。
:type qubits_idx: Union[Iterable[int], int, str], optional
:param num_qubits: 总共的量子比特数量,默认为 ``None``。
:type num_qubits: int, optional
:param depth: 层数,默认为 ``1``。
:type depth: int, optional
.. py:method:: t(qubits_idx='full', num_qubits=None, depth=1) .. py:method:: t(qubits_idx='full', num_qubits=None, depth=1)
添加单量子比特 T 门。 添加单量子比特 T 门。
...@@ -118,7 +135,28 @@ paddle\_quantum.ansatz.circuit ...@@ -118,7 +135,28 @@ paddle\_quantum.ansatz.circuit
:type num_qubits: int, optional :type num_qubits: int, optional
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: tdg(qubits_idx='full', num_qubits=None, depth=1)
添加单量子比特 T dagger (逆T)门。
其矩阵形式为:
.. math::
T ^\dagger =
\begin{bmatrix}
1 & 0\ \
0 & e^\frac{i\pi}{4}
\end{bmatrix}
:param qubits_idx: 作用在的量子比特的编号,默认为 ``'full'``。
:type qubits_idx: Union[Iterable[int], int, str], optional
:param num_qubits: 总共的量子比特数量,默认为 ``None``。
:type num_qubits: int, optional
:param depth: 层数,默认为 ``1``。
:type depth: int, optional
.. py:method:: x(qubits_idx='full', num_qubits=None, depth=1) .. py:method:: x(qubits_idx='full', num_qubits=None, depth=1)
添加单量子比特 X 门。 添加单量子比特 X 门。
...@@ -767,7 +805,7 @@ paddle\_quantum.ansatz.circuit ...@@ -767,7 +805,7 @@ paddle\_quantum.ansatz.circuit
:param param_sharing: 同一层中的量子门是否共享参数,默认为 ``False``。 :param param_sharing: 同一层中的量子门是否共享参数,默认为 ``False``。
:type param_sharing: bool, optional :type param_sharing: bool, optional
.. py:method:: oracle(oracle, qubits_idx, num_qubits=None, depth=1) .. py:method:: oracle(oracle, qubits_idx, num_qubits=None, depth=1, gate_name='0', latex_name=None, plot_width=None)
添加一个 oracle 门。 添加一个 oracle 门。
...@@ -781,8 +819,12 @@ paddle\_quantum.ansatz.circuit ...@@ -781,8 +819,12 @@ paddle\_quantum.ansatz.circuit
:type depth: int, optional :type depth: int, optional
:param gate_name: oracle 的名字,默认为 ``O``。 :param gate_name: oracle 的名字,默认为 ``O``。
:type gate_name: str, optional :type gate_name: str, optional
:param latex_name: oracle 的Latex名字,默认为 None, 此时用 gate_name。
:type latex_name: str, optional
:param plot_width: 电路图中此门的宽度,默认为None,此时与门名称成比例。
:type gate_name: float, optional
.. py:method:: control_oracle(oracle, qubits_idx, num_qubits=None, depth=1) .. py:method:: control_oracle(oracle, qubits_idx, num_qubits=None, depth=1, gate_name='0', latex_name=None, plot_width=None)
添加一个受控 oracle 门。 添加一个受控 oracle 门。
...@@ -796,6 +838,10 @@ paddle\_quantum.ansatz.circuit ...@@ -796,6 +838,10 @@ paddle\_quantum.ansatz.circuit
:type depth: int, optional :type depth: int, optional
:param gate_name: oracle 的名字,默认为 ``cO``。 :param gate_name: oracle 的名字,默认为 ``cO``。
:type gate_name: str, optional :type gate_name: str, optional
:param latex_name: oracle 的Latex名字,默认为 None, 此时用 gate_name。
:type latex_name: str, optional
:param plot_width: 电路图中此门的宽度,默认为None,此时与门名称成比例。
:type gate_name: float, optional
.. py:method:: collapse(qubits_idx='full', num_qubits=None, desired_result=None, if_print=False, measure_basis='z') .. py:method:: collapse(qubits_idx='full', num_qubits=None, desired_result=None, if_print=False, measure_basis='z')
...@@ -936,13 +982,13 @@ paddle\_quantum.ansatz.circuit ...@@ -936,13 +982,13 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: generalized_amplitude_damping(gamma, qubits_idx='full', num_qubits=None) .. py:method:: generalized_amplitude_damping(gamma, prob, qubits_idx='full', num_qubits=None)
添加广义振幅阻尼信道。 添加广义振幅阻尼信道。
:param gamma: 减振概率。 :param gamma: 减振概率,其值应该在 :math:`[0, 1]` 区间内
:type prob: Union[paddle.Tensor, float] :type prob: Union[paddle.Tensor, float]
:param prob: 激发概率。 :param prob: 激发概率,其值应该在 :math:`[0, 1]` 区间内
:type prob: Union[paddle.Tensor, float] :type prob: Union[paddle.Tensor, float]
:param qubits_idx: 作用在的量子比特的编号, 默认为 ``'full'``。 :param qubits_idx: 作用在的量子比特的编号, 默认为 ``'full'``。
:type qubits_idx: Union[Iterable[int], int, str], optional :type qubits_idx: Union[Iterable[int], int, str], optional
...@@ -1006,6 +1052,17 @@ paddle\_quantum.ansatz.circuit ...@@ -1006,6 +1052,17 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: mixed_unitary_channel(num_unitary, qubits_idx='full', num_qubits=None)
添加混合酉矩阵信道
:param num_unitary: 用于构成信道的酉矩阵的数量。
:type num_unitary: Union[paddle.Tensor, Iterable[int]]
:param qubits_idx: 作用在的量子比特的编号, 默认为 ``'full'``。
:type qubits_idx: Union[Iterable[int], int, str], optional
:param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional
.. py:method:: kraus_repr(kraus_oper, qubits_idx, num_qubits=None) .. py:method:: kraus_repr(kraus_oper, qubits_idx, num_qubits=None)
添加一个 Kraus 表示的自定义量子信道。 添加一个 Kraus 表示的自定义量子信道。
...@@ -1040,3 +1097,34 @@ paddle\_quantum.ansatz.circuit ...@@ -1040,3 +1097,34 @@ paddle\_quantum.ansatz.circuit
:return: 每个比特上的量子门的插入历史 :return: 每个比特上的量子门的插入历史
:rtype: List[List[Tuple[Dict[str, Union[str, List[int], paddle.Tensor]], int]]] :rtype: List[List[Tuple[Dict[str, Union[str, List[int], paddle.Tensor]], int]]]
.. py:method:: plot(save_path, dpi=100, show=True, output=False, scale=1.0, tex=False)
画出量子电路图
:param save_path: 图像保存的路径,默认为 ``None``。
:type save_path: str, optional
:param dpi: 每英寸像素数,这里指分辨率, 默认为 `100`。
:type dpi: int, optional
:param show: 是否执行 ``plt.show()``, 默认为 ``True``。
:type show: bool, optional
:param output: 是否返回 ``matplotlib.figure.Figure`` 实例,默认为 ``False``。
:type output: bool, optional
:param scale: ``figure`` 的 ``scale`` 系数,默认为 `1.0`。
:type scale: float, optional
:param tex: 一个布尔变量,用于控制是否使用 TeX 字体,默认为 ``False``。
:type tex: bool, optional
:return: 根据 ``output`` 参数返回 ``matplotlib.figure.Figure`` 实例或 ``None``。
:rtype: Union[None, matplotlib.figure.Figure]
.. note::
使用 ``plt.show()`` 或许会导致一些问题,但是在保存图片时不会发生。如果电路太深可能会有一些图形无法显示。如果设置 ``tex = True`` 则需要在你的系统上安装 TeX 及其相关的依赖包。更多细节参考 https://matplotlib.org/stable/gallery/text_labels_and_annotations/tex_demo.html
.. py:method:: extend(cir)
量子电路扩展
:param cir: 量子电路。
:type cir: Circuit
\ No newline at end of file
...@@ -3,6 +3,21 @@ paddle\_quantum.ansatz.vans ...@@ -3,6 +3,21 @@ paddle\_quantum.ansatz.vans
可变结构电路的功能实现。 可变结构电路的功能实现。
.. py:function:: cir_decompose(cir)
将电路中的 Layer 分解成量子门, 如果需要的话可以把所有参数门的输入转为可训练参数
:param cir: 待分解电路
:type cir: Circuit
:param trainable: 是否将分解后的参数量子门输入转为参数baidu
:type trainable: bool, optional
:return: 分解后的电路
:rtype: Circuit
.. note::
该量子电路稳定支持原生门,不支持 oracle 等其他自定义量子门。
.. py:class:: Inserter .. py:class:: Inserter
基类: :py:class:`object` 基类: :py:class:`object`
...@@ -39,21 +54,6 @@ paddle\_quantum.ansatz.vans ...@@ -39,21 +54,6 @@ paddle\_quantum.ansatz.vans
:return: 简化后的电路。 :return: 简化后的电路。
:rtype: Circuit :rtype: Circuit
.. py:function:: cir_decompose(cir)
将电路中的 Layer 分解成量子门, 如果需要的话可以把所有参数门的输入转为可训练参数
:param cir: 待分解电路
:type cir: Circuit
:param trainable: 是否将分解后的参数量子门输入转为参数
:type trainable: bool, optional
:return: 分解后的电路
:rtype: Circuit
.. note::
该量子电路稳定支持原生门,不支持 oracle 等其他自定义量子门。
.. py:class:: VAns(n, loss_func, *loss_func_args, epsilon=0.1, insert_rate=2, iter=100, iter_out=10, LR =0.1, threshold=0.002, accept_wall=100, zero_init_state=True) .. py:class:: VAns(n, loss_func, *loss_func_args, epsilon=0.1, insert_rate=2, iter=100, iter_out=10, LR =0.1, threshold=0.002, accept_wall=100, zero_init_state=True)
基类: :py:class:`object` 基类: :py:class:`object`
......
...@@ -157,10 +157,10 @@ paddle\_quantum.channel.common ...@@ -157,10 +157,10 @@ paddle\_quantum.channel.common
.. math:: .. math::
E_0 = \sqrt{1-p} I, E_0 = \sqrt{1-3p/4} I,
E_1 = \sqrt{p/3} X, E_1 = \sqrt{p/4} X,
E_2 = \sqrt{p/3} Y, E_2 = \sqrt{p/4} Y,
E_3 = \sqrt{p/3} Z. E_3 = \sqrt{p/4} Z.
:param prob: 该信道的参数,其值应该在 :math:`[0, 1]` 区间内。 :param prob: 该信道的参数,其值应该在 :math:`[0, 1]` 区间内。
:type prob: Union[paddle.Tensor, float] :type prob: Union[paddle.Tensor, float]
...@@ -169,6 +169,11 @@ paddle\_quantum.channel.common ...@@ -169,6 +169,11 @@ paddle\_quantum.channel.common
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. note::
该功能的实现逻辑已更新。
当前版本请参考 M.A.Nielsen and I.L.Chuang 所著 Quantum Computation and Quantum Information 第10版中的 (8.102) 式。
参考文献: Nielsen, M., & Chuang, I. (2010). Quantum Computation and Quantum Information: 10th Anniversary Edition. Cambridge: Cambridge University Press. doi:10.1017/CBO9780511976667
.. py:class:: PauliChannel(prob, qubits_idx='full', num_qubits) .. py:class:: PauliChannel(prob, qubits_idx='full', num_qubits)
基类::py:class:`paddle_quantum.channel.base.Channel` 基类::py:class:`paddle_quantum.channel.base.Channel`
...@@ -249,3 +254,16 @@ paddle\_quantum.channel.common ...@@ -249,3 +254,16 @@ paddle\_quantum.channel.common
.. note:: .. note::
时间常数必须满足 :math:`T_2 \le T_1`,见参考文献 https://arxiv.org/abs/2101.02109。 时间常数必须满足 :math:`T_2 \le T_1`,见参考文献 https://arxiv.org/abs/2101.02109。
.. py:class:: MixedUnitaryChannel(num_unitary, qubits_idx='full', num_qubits=None)
基类::py:class:`paddle_quantum.channel.base.Channel`
混合酉矩阵信道。
:param num_unitary: 用于构成信道的酉矩阵数量。
:type num_unitary: int
:param qubits_idx: 作用在的量子比特的编号,默认为 ``'full'``。
:type qubits_idx: Union[Iterable[int], int, str], optional
:param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional
\ No newline at end of file
...@@ -24,6 +24,29 @@ paddle\_quantum.gate.base ...@@ -24,6 +24,29 @@ paddle\_quantum.gate.base
生成量子门的历史记录 生成量子门的历史记录
.. py:method:: set_gate_info(**kwargs)
设置 `gate_info` 的接口
:param kwargs: 用于设置 `gate_info` 的参数。
:type kwargs: Any
.. py:method:: display_in_circuit(ax, x)
画出量子电路图,在 `Circuit` 类中被调用。
:param ax: ``matplotlib.axes.Axes`` 的实例.
:type ax: matplotlib.axes.Axes
:param x: 开始的水平位置。
:type x: float
:return: 占用的总宽度。
:rtype: float
.. note::
使用者可以覆写此函数,从而自定义显示方式。
.. py:class:: ParamGate .. py:class:: ParamGate
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.Gate`
......
...@@ -11,3 +11,4 @@ paddle\_quantum.gate.functional ...@@ -11,3 +11,4 @@ paddle\_quantum.gate.functional
paddle_quantum.gate.functional.base paddle_quantum.gate.functional.base
paddle_quantum.gate.functional.multi_qubit_gate paddle_quantum.gate.functional.multi_qubit_gate
paddle_quantum.gate.functional.single_qubit_gate paddle_quantum.gate.functional.single_qubit_gate
paddle_quantum.gate.functional.visual
...@@ -33,6 +33,21 @@ paddle\_quantum.gate.functional.single\_qubit\_gate ...@@ -33,6 +33,21 @@ paddle\_quantum.gate.functional.single\_qubit\_gate
:return: 输出态。 :return: 输出态。
:rtype: paddle_quantum.State :rtype: paddle_quantum.State
.. py:function:: sdg(state, qubit_idx, dtype, backend)
在输入态上作用一个S dagger(逆S)门。
:param state: 输入态。
:type state: paddle_quantum.State
:param qubit_idx: 作用在量子比特的编号。
:type qubit_idx: int
:param dtype: 数据的类型。
:type dtype: str
:param backend: 运行模拟的后端。
:type backend: paddle_quantum.Backend
:return: 输出态。
:rtype: paddle_quantum.State
.. py:function:: t(state, qubit_idx, dtype, backend) .. py:function:: t(state, qubit_idx, dtype, backend)
在输入态上作用一个 T 门。 在输入态上作用一个 T 门。
...@@ -48,6 +63,21 @@ paddle\_quantum.gate.functional.single\_qubit\_gate ...@@ -48,6 +63,21 @@ paddle\_quantum.gate.functional.single\_qubit\_gate
:return: 输出态。 :return: 输出态。
:rtype: paddle_quantum.State :rtype: paddle_quantum.State
.. py:function:: tdg(state, qubit_idx, dtype, backend)
在输入态上作用一个T dagger(逆T)门。
:param state: 输入态。
:type state: paddle_quantum.State
:param qubit_idx: 作用在量子比特的编号。
:type qubit_idx: int
:param dtype: 数据的类型。
:type dtype: str
:param backend: 运行模拟的后端。
:type backend: paddle_quantum.Backend
:return: 输出态。
:rtype: paddle_quantum.State
.. py:function:: x(state, qubit_idx, dtype, backend) .. py:function:: x(state, qubit_idx, dtype, backend)
在输入态上作用一个 X 门。 在输入态上作用一个 X 门。
......
paddle\_quantum.gate.functional.visual
=======================================
可视化 ``paddle_quantum.ansatz.Circuit`` 类中量子门的函数
.. py:function:: scale_circuit_plot_param(scale)
根据 ``scale`` 修改 ``__CIRCUIT_PLOT_PARAM`` 的字典参数。
:param scale: 画图参数的缩放标量。
:type scale: float
.. py:function:: set_circuit_plot_param(**kwargs)
自定义画图参数字典 ``__CIRCUIT_PLOT_PARAM``。
:param kwargs: 需要更新的字典 ``__CIRCUIT_PLOT_PARAM`` 参数。
:type scale: Any
.. py:function:: get_circuit_plot_param()
输出画图参数字典 ``__CIRCUIT_PLOT_PARAM``。
:return: ``__CIRCUIT_PLOT_PARAM`` 字典的拷贝。
:rtype: dict
.. py:function:: reset_circuit_plot_param()
重置画图参数字典 ``__CIRCUIT_PLOT_PARAM``。
...@@ -140,7 +140,7 @@ paddle\_quantum.gate.layer ...@@ -140,7 +140,7 @@ paddle\_quantum.gate.layer
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:class:: QAOALayer(Gate) .. py:class:: QAOALayerWeighted(Gate)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.Gate`
......
...@@ -49,6 +49,29 @@ paddle\_quantum.gate.single\_qubit\_gate ...@@ -49,6 +49,29 @@ paddle\_quantum.gate.single\_qubit\_gate
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:class:: Sdg(qubits_idx='full', num_qubits=None, depth=1)
基类::py:class:`paddle_quantum.gate.base.Gate`
单量子比特S dagger(逆S)门。
其矩阵形式为:
.. math::
S ^\dagger =
\begin{bmatrix}
1 & 0\ \
0 & -i
\end{bmatrix}
:param qubits_idx:作用在的量子比特的编号,默认为``'full'``。
:type qubits_idx: Union[Iterable, int, str], optional
:param num_qubits:总的量子比特个数,默认为``None``。
:type num_qubits: int, optional
:param depth: 层数,默认为``1``。
:type depth: int, optional
.. py:class:: T(qubits_idx='full', num_qubits=None, depth=1) .. py:class:: T(qubits_idx='full', num_qubits=None, depth=1)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.Gate`
...@@ -72,6 +95,29 @@ paddle\_quantum.gate.single\_qubit\_gate ...@@ -72,6 +95,29 @@ paddle\_quantum.gate.single\_qubit\_gate
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:class:: Tdg(qubits_idx='full', num_qubits=None, depth=1)
基类::py:class:`paddle_quantum.gate.base.Gate`
单量子比特T dagger(逆T)门。
其矩阵形式为:
.. math::
T ^\dagger =
\begin{bmatrix}
1 & 0\ \
0 & e^{-\frac{i\pi}{4}}
\end{bmatrix}
:param qubits_idx:作用在的量子比特的编号,默认为``'full'``。
:type qubits_idx: Union[Iterable, int, str], optional
:param num_qubits:总的量子比特个数,默认为``None``。
:type num_qubits: int, optional
:param depth: 层数,默认为``1``。
:type depth: int, optional
.. py:class:: X(qubits_idx='full', num_qubits=None, depth=1) .. py:class:: X(qubits_idx='full', num_qubits=None, depth=1)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.Gate`
......
...@@ -8,7 +8,7 @@ paddle\_quantum.linalg ...@@ -8,7 +8,7 @@ paddle\_quantum.linalg
计算矩阵范数 计算矩阵范数
:param mat: 矩阵 :param mat: 矩阵
:type mat: paddle.Tensor :type mat: Union[np.ndarray, paddle.Tensor, State]
:return: 范数 :return: 范数
:rtype: float :rtype: float
...@@ -18,17 +18,17 @@ paddle\_quantum.linalg ...@@ -18,17 +18,17 @@ paddle\_quantum.linalg
计算矩阵的转置共轭 计算矩阵的转置共轭
:param mat: 矩阵 :param mat: 矩阵
:type mat: paddle.Tensor :type mat: Union[np.ndarray, paddle.Tensor]
:return: 矩阵的转置共轭 :return: 矩阵的转置共轭
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: is_hermitian(mat, eps=1e-6) .. py:function:: is_hermitian(mat, eps=1e-6)
验证矩阵 ``P`` 是否为厄密矩阵 验证矩阵 ``P`` 是否为厄密矩阵
:param mat: 厄密矩阵 :param mat: 厄密矩阵
:type mat: paddle.Tensor :type mat: Union[np.ndarray, paddle.Tensor]
:param eps: 容错率 :param eps: 容错率
:type eps: float, optional :type eps: float, optional
...@@ -40,7 +40,7 @@ paddle\_quantum.linalg ...@@ -40,7 +40,7 @@ paddle\_quantum.linalg
验证矩阵 ``P`` 是否为映射算子 验证矩阵 ``P`` 是否为映射算子
:param mat: 映射算子 :param mat: 映射算子
:type mat: paddle.Tensor :type mat: Union[np.ndarray, paddle.Tensor]
:param eps: 容错率 :param eps: 容错率
:type eps: float, optional :type eps: float, optional
...@@ -52,7 +52,7 @@ paddle\_quantum.linalg ...@@ -52,7 +52,7 @@ paddle\_quantum.linalg
验证矩阵 ``P`` 是否为酉矩阵 验证矩阵 ``P`` 是否为酉矩阵
:param mat: 酉矩阵 :param mat: 酉矩阵
:type mat: paddle.Tensor :type mat: Union[np.ndarray, paddle.Tensor]
:param eps: 容错率 :param eps: 容错率
:type eps: float, optional :type eps: float, optional
...@@ -116,11 +116,23 @@ paddle\_quantum.linalg ...@@ -116,11 +116,23 @@ paddle\_quantum.linalg
:param num_qubits: 量子比特数 n :param num_qubits: 量子比特数 n
:type num_qubits: int :type num_qubits: int
:param is_unitary: 厄密矩阵块是否是酉矩阵的 1/2 :param is_unitary: 厄密矩阵块是否是酉矩阵的 1/2
:type is_unitary: bool :type is_unitary: bool, optional
:return: 一个左上半部分为厄密矩阵的 :math:`2^n \times 2^n` 酉矩阵 (n 为量子比特数) :return: 一个左上半部分为厄密矩阵的 :math:`2^n \times 2^n` 酉矩阵 (n 为量子比特数)
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: block_enc_herm(mat, num_block_qubits)
生成厄密矩阵的分组编码
:param mat: 用于分组编码的矩阵
:type mat: Union[np.ndarray, paddle.Tensor]
:param num_block_qubits: 用于分组编码的辅助量子比特数
:type num_block_qubits: int, optional
:return: 返回分组编码后的酉矩阵
:rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: haar_orthogonal(num_qubits) .. py:function:: haar_orthogonal(num_qubits)
生成一个服从 Haar random 的正交矩阵。采样算法参考文献: arXiv:math-ph/0609050v2 生成一个服从 Haar random 的正交矩阵。采样算法参考文献: arXiv:math-ph/0609050v2
...@@ -167,16 +179,54 @@ paddle\_quantum.linalg ...@@ -167,16 +179,54 @@ paddle\_quantum.linalg
:return: 一个 :math:`2^n \times 2^n` 密度矩阵 (n 为量子比特数) :return: 一个 :math:`2^n \times 2^n` 密度矩阵 (n 为量子比特数)
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: direct_sum(A,B)
计算A和B的直和
:param A: :math:`m \times n` 的矩阵
:type A: Union[np.ndarray, paddle.Tensor]
:param B: :math:`p \times q` 的矩阵
:type B: Union[np.ndarray, paddle.Tensor]
:return: A和B的直和,维度为 :math:`(m + p) \times (n + q)`
:rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: NKron(matrix_A, matrix_B, *args) .. py:function:: NKron(matrix_A, matrix_B, *args)
计算两个及以上的矩阵的克罗内克乘积 计算两个及以上的矩阵的克罗内克乘积
:param matrix_A: 矩阵 :param matrix_A: 矩阵
:type num_qubits: Union[np.ndarray, paddle.Tensor] :type matrix_A: Union[np.ndarray, paddle.Tensor]
:param matrix_B: 矩阵 :param matrix_B: 矩阵
:type matrix_B: Union[np.ndarray, paddle.Tensor] :type matrix_B: Union[np.ndarray, paddle.Tensor]
:param \*args: 更多矩阵 :param \*args: 更多矩阵
:type \*args: Union[np.ndarray, paddle.Tensor] :type \*args: Union[np.ndarray, paddle.Tensor]
.. code-block:: python
from paddle_quantum.linalg import density_op_random, NKron
A = density_op_random(2)
B = density_op_random(2)
C = density_op_random(2)
result = NKron(A, B, C)
.. note::
上述代码块的 ``result`` 应为 :math:`A \otimes B \otimes C`
:return: 克罗内克乘积 :return: 克罗内克乘积
:rtype: Union[np.ndarray, paddle.Tensor] :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: herm_transform(fcn, mat, ignore_zero)
厄密矩阵的函数变换
:param fcn: 可以展开成泰勒级数的函数 `f`
:type fcn: Callable[[float], float]
:param mat: 厄密矩阵 :math:`H`
:type mat: Union[paddle.Tensor, np.ndarray, State]
:param ignore_zero: 是否忽略特征值0所在的特征空间,默认为 ``False``
:type ignore_zero: bool, optional
:return: :math:`f(H)`
:rtype: paddle.Tensor
\ No newline at end of file
...@@ -8,7 +8,7 @@ paddle\_quantum.qinfo ...@@ -8,7 +8,7 @@ paddle\_quantum.qinfo
计算量子态的偏迹。 计算量子态的偏迹。
:param state: 输入的量子态。 :param state: 输入的量子态。
:type state: Union[paddle_quantum.State, paddle.Tensor] :type state: Union[np.ndarray, paddle.Tensor, State]
:param dim1: 系统A的维数。 :param dim1: 系统A的维数。
:type dim1: int :type dim1: int
:param dim2: 系统B的维数。 :param dim2: 系统B的维数。
...@@ -17,19 +17,19 @@ paddle\_quantum.qinfo ...@@ -17,19 +17,19 @@ paddle\_quantum.qinfo
:type A_or_B: int :type A_or_B: int
:return: 输入的量子态的偏迹。 :return: 输入的量子态的偏迹。
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor, State]
.. py:function:: partial_trace_discontiguous(state, preserve_qubits=None) .. py:function:: partial_trace_discontiguous(state, preserve_qubits=None)
计算量子态的偏迹,可选取任意子系统。 计算量子态的偏迹,可选取任意子系统。
:param state: 输入的量子态。 :param state: 输入的量子态。
:type state: Union[paddle_quantum.State, paddle.Tensor] :type state: Union[np.ndarray, paddle.Tensor, State]
:param preserve_qubits: 要保留的量子比特,默认为 None,表示全保留。 :param preserve_qubits: 要保留的量子比特,默认为 None,表示全保留。
:type preserve_qubits: list, optional :type preserve_qubits: list, optional
:return: 所选子系统的量子态偏迹。 :return: 所选子系统的量子态偏迹。
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor, State]
.. py:function:: trace_distance(rho, sigma) .. py:function:: trace_distance(rho, sigma)
...@@ -40,12 +40,12 @@ paddle\_quantum.qinfo ...@@ -40,12 +40,12 @@ paddle\_quantum.qinfo
D(\rho, \sigma) = 1 / 2 * \text{tr}|\rho-\sigma| D(\rho, \sigma) = 1 / 2 * \text{tr}|\rho-\sigma|
:param rho: 量子态的密度矩阵形式。 :param rho: 量子态的密度矩阵形式。
:type rho: Union[paddle_quantum.State, paddle.Tensor] :type rho: Union[np.ndarray, paddle.Tensor, State]
:param sigma: 量子态的密度矩阵形式。 :param sigma: 量子态的密度矩阵形式。
:type sigma: Union[paddle_quantum.State, paddle.Tensor] :type sigma: Union[np.ndarray, paddle.Tensor, State]
:return: 输入的量子态之间的迹距离。 :return: 输入的量子态之间的迹距离。
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: state_fidelity(rho, sigma) .. py:function:: state_fidelity(rho, sigma)
...@@ -56,11 +56,11 @@ paddle\_quantum.qinfo ...@@ -56,11 +56,11 @@ paddle\_quantum.qinfo
F(\rho, \sigma) = \text{tr}(\sqrt{\sqrt{\rho}\sigma\sqrt{\rho}}) F(\rho, \sigma) = \text{tr}(\sqrt{\sqrt{\rho}\sigma\sqrt{\rho}})
:param rho: 量子态的密度矩阵形式。 :param rho: 量子态的密度矩阵形式。
:type rho: Union[paddle_quantum.State, paddle.Tensor] :type rho: Union[np.ndarray, paddle.Tensor, State]
:param sigma: 量子态的密度矩阵形式。 :param sigma: 量子态的密度矩阵形式。
:type sigma: Union[paddle_quantum.State, paddle.Tensor] :type sigma: Union[np.ndarray, paddle.Tensor, State]
:return: 输入的量子态之间的保真度。 :return: 输入的量子态之间的保真度。
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: gate_fidelity(U, V) .. py:function:: gate_fidelity(U, V)
...@@ -73,12 +73,12 @@ paddle\_quantum.qinfo ...@@ -73,12 +73,12 @@ paddle\_quantum.qinfo
:math:`U` 是一个 :math:`2^n\times 2^n` 的 Unitary 矩阵。 :math:`U` 是一个 :math:`2^n\times 2^n` 的 Unitary 矩阵。
:param U: 量子门 :math:`U` 的酉矩阵形式 :param U: 量子门 :math:`U` 的酉矩阵形式
:type U: paddle.Tensor :type U: Union[np.ndarray, paddle.Tensor]
:param V: 量子门 :math:`V` 的酉矩阵形式 :param V: 量子门 :math:`V` 的酉矩阵形式
:type V: paddle.Tensor :type V: Union[np.ndarray, paddle.Tensor]
:return: 输入的量子门之间的保真度 :return: 输入的量子门之间的保真度
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: purity(rho) .. py:function:: purity(rho)
...@@ -89,12 +89,12 @@ paddle\_quantum.qinfo ...@@ -89,12 +89,12 @@ paddle\_quantum.qinfo
P = \text{tr}(\rho^2) P = \text{tr}(\rho^2)
:param rho: 量子态的密度矩阵形式。 :param rho: 量子态的密度矩阵形式。
:type rho: Union[paddle_quantum.State, paddle.Tensor] :type rho: Union[np.ndarray, paddle.Tensor, State]
:return: 输入的量子态的纯度。 :return: 输入的量子态的纯度。
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: von_neumann_entropy(rho) .. py:function:: von_neumann_entropy(rho, base)
计算量子态的冯诺依曼熵。 计算量子态的冯诺依曼熵。
...@@ -103,12 +103,14 @@ paddle\_quantum.qinfo ...@@ -103,12 +103,14 @@ paddle\_quantum.qinfo
S = -\text{tr}(\rho \log(\rho)) S = -\text{tr}(\rho \log(\rho))
:param rho: 量子态的密度矩阵形式。 :param rho: 量子态的密度矩阵形式。
:type rho: Union[paddle_quantum.State, paddle.Tensor] :type rho: Union[np.ndarray, paddle.Tensor, State]
:param base: 对数的底。默认为2。
:type base: int, optional
:return: 输入的量子态的冯诺依曼熵。 :return: 输入的量子态的冯诺依曼熵。
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: relative_entropy(rho, sig) .. py:function:: relative_entropy(rho, sig, base)
计算两个量子态的相对熵。 计算两个量子态的相对熵。
...@@ -117,12 +119,14 @@ paddle\_quantum.qinfo ...@@ -117,12 +119,14 @@ paddle\_quantum.qinfo
S(\rho \| \sigma)=\text{tr} \rho(\log \rho-\log \sigma) S(\rho \| \sigma)=\text{tr} \rho(\log \rho-\log \sigma)
:param rho: 量子态的密度矩阵形式 :param rho: 量子态的密度矩阵形式
:type rho: Union[paddle_quantum.State, paddle.Tensor] :type rho: Union[np.ndarray, paddle.Tensor, State]
:param sig: 量子态的密度矩阵形式 :param sig: 量子态的密度矩阵形式
:type sig: Union[paddle_quantum.State, paddle.Tensor] :type sig: Union[np.ndarray, paddle.Tensor, State]
:param base: 对数的底。默认为2.
:type base: int, optional
:return: 输入的量子态之间的相对熵 :return: 输入的量子态之间的相对熵
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: random_pauli_str_generator(n, terms=3) .. py:function:: random_pauli_str_generator(n, terms=3)
...@@ -163,51 +167,51 @@ paddle\_quantum.qinfo ...@@ -163,51 +167,51 @@ paddle\_quantum.qinfo
计算输入量子态的 partial transpose :math:`\rho^{T_A}`。 计算输入量子态的 partial transpose :math:`\rho^{T_A}`。
:param density_op: 量子态的密度矩阵形式。 :param density_op: 量子态的密度矩阵形式。
:type density_op: Union[paddle_quantum.State, paddle.Tensor] :type density_op: Union[np.ndarray, paddle.Tensor, State]
:param sub_system: 1或2,表示关于哪个子系统进行 partial transpose,默认为第二个。 :param sub_system: 1或2,表示关于哪个子系统进行 partial transpose,默认为第二个。
:type sub_system: int, optional :type sub_system: int, optional
:return: 输入的量子态的 partial transpose :return: 输入的量子态的 partial transpose
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: partial_transpose(density_op, n) .. py:function:: partial_transpose(density_op, n)
计算输入量子态的 partial transpose :math:`\rho^{T_A}`。 计算输入量子态的 partial transpose :math:`\rho^{T_A}`。
:param density_op: 量子态的密度矩阵形式。 :param density_op: 量子态的密度矩阵形式。
:type density_op: Union[paddle_quantum.State, paddle.Tensor] :type density_op: Union[np.ndarray, paddle.Tensor, State]
:param n: 需要转置系统的量子比特数量。 :param n: 需要转置系统的量子比特数量。
:type n: int :type n: int
:return: 输入的量子态的 partial transpose。 :return: 输入的量子态的 partial transpose。
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: negativity(density_op) .. py:function:: negativity(density_op)
计算输入量子态的 Negativity :math:`N = ||\frac{\rho^{T_A}-1}{2}||`。 计算输入量子态的 Negativity :math:`N = ||\frac{\rho^{T_A}-1}{2}||`。
:param density_op: 量子态的密度矩阵形式。 :param density_op: 量子态的密度矩阵形式。
:type density_op: Union[paddle_quantum.State, paddle.Tensor] :type density_op: Union[np.ndarray, paddle.Tensor, State]
:return: 输入的量子态的 Negativity。 :return: 输入的量子态的 Negativity。
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: logarithmic_negativity(density_op) .. py:function:: logarithmic_negativity(density_op)
计算输入量子态的 Logarithmic Negativity :math:`E_N = ||\rho^{T_A}||`。 计算输入量子态的 Logarithmic Negativity :math:`E_N = ||\rho^{T_A}||`。
:param density_op: 量子态的密度矩阵形式。 :param density_op: 量子态的密度矩阵形式。
:type density_op: Union[paddle_quantum.State, paddle.Tensor] :type density_op: Union[np.ndarray, paddle.Tensor, State]
:return: 输入的量子态的 Logarithmic Negativity。 :return: 输入的量子态的 Logarithmic Negativity。
:rtype: paddle.Tensor :rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: is_ppt(density_op) .. py:function:: is_ppt(density_op)
计算输入量子态是否满足 PPT 条件。 计算输入量子态是否满足 PPT 条件。
:param density_op: 量子态的密度矩阵形式。 :param density_op: 量子态的密度矩阵形式。
:type density_op: Union[paddle_quantum.State, paddle.Tensor] :type density_op: Union[np.ndarray, paddle.Tensor, State]
:return: 输入的量子态是否满足 PPT 条件。 :return: 输入的量子态是否满足 PPT 条件。
:rtype: bool :rtype: bool
...@@ -217,7 +221,7 @@ paddle\_quantum.qinfo ...@@ -217,7 +221,7 @@ paddle\_quantum.qinfo
计算输入量子态的施密特分解 :math:`\lvert\psi\rangle=\sum_ic_i\lvert i_A\rangle\otimes\lvert i_B \rangle`。 计算输入量子态的施密特分解 :math:`\lvert\psi\rangle=\sum_ic_i\lvert i_A\rangle\otimes\lvert i_B \rangle`。
:param psi: 量子态的向量形式,形状为(2**n)。 :param psi: 量子态的向量形式,形状为(2**n)。
:type psi: Union[paddle_quantum.State, paddle.Tensor] :type psi: Union[np.ndarray, paddle.Tensor, State]
:param sys_A: 包含在子系统 A 中的 qubit 下标(其余 qubit 包含在子系统B中),默认为量子态 :math:`\lvert \psi\rangle` 的前半数 qubit。 :param sys_A: 包含在子系统 A 中的 qubit 下标(其余 qubit 包含在子系统B中),默认为量子态 :math:`\lvert \psi\rangle` 的前半数 qubit。
:type sys_A: List[int], optional :type sys_A: List[int], optional
...@@ -228,7 +232,7 @@ paddle\_quantum.qinfo ...@@ -228,7 +232,7 @@ paddle\_quantum.qinfo
- 由子系统A的基 :math:`\lvert i_A\rangle` 组成的高维数组,形状为 ``(k, 2**m, 1)``。 - 由子系统A的基 :math:`\lvert i_A\rangle` 组成的高维数组,形状为 ``(k, 2**m, 1)``。
- 由子系统B的基 :math:`\lvert i_B\rangle` 组成的高维数组,形状为 ``(k, 2**l, 1)``。 - 由子系统B的基 :math:`\lvert i_B\rangle` 组成的高维数组,形状为 ``(k, 2**l, 1)``。
:rtype: Tuple[paddle.Tensor] :rtype: Union[Tuple[paddle.Tensor, paddle.Tensor, paddle.Tensor], Tuple[np.ndarray, np.ndarray, np.ndarray]]
.. py:function:: image_to_density_matrix(image_filepath) .. py:function:: image_to_density_matrix(image_filepath)
...@@ -258,22 +262,74 @@ paddle\_quantum.qinfo ...@@ -258,22 +262,74 @@ paddle\_quantum.qinfo
:return: 估计可观测量 :math:`H` 的期望值。 :return: 估计可观测量 :math:`H` 的期望值。
:rtype: float :rtype: float
.. py:function:: tensor_product(state_a, state_b, *args) .. py:function:: tensor_state(state_a, state_b, *args)
计算输入的量子态(至少两个)的直积形式, 输出将自动返回 State 实例 计算输入的量子态(至少两个)的直积形式, 输出将自动返回 State 实例
:param state_a: 量子态A :param state_a: 量子态A
:type state_a: Union[State, paddle.Tensor] :type state_a: State
:param state_b: 量子态B :param state_b: 量子态B
:type state_b: Union[State, paddle.Tensor] :type state_b: State
:param args: 其他量子态 :param args: 其他量子态
:type args: Union[State, paddle.Tensor] :type args: State
:raises NotImplementedError: 当前只接收输入类型为 State 或 paddle.Tensor
.. note:: .. note::
使用的 backend 必须为 DensityMatrix 需要注意输入态使用的 backend;
若输入数据为 ``paddle.Tensor`` 或者 ``numpy.ndarray``,请使用 ``paddle_quantum.linalg.NKron`` 函数处理。
:return: 输入量子态的直积 :return: 输入量子态的直积
:rtype: State :rtype: State
\ No newline at end of file
.. py:function:: diamond_norm(channel_repr, dim_io, **kwargs)
计算输入的菱形范数
:param channel_repr: 信道对应的表示, ``ChoiRepr`` 或 ``KrausRepr`` 或 ``StinespringRepr`` 或 ``paddle.Tensor``。
:type channel_repr: Union[ChoiRepr, KrausRepr, StinespringRepr, paddle.Tensor]
:param dim_io: 输入和输出的维度。
:type dim_io: Union[int, Tuple[int, int]], optional.
:param kwargs: 使用cvx所需的参数。
:type kwargs: Any
:raises RuntimeError: ``channel_repr`` 必须是 ``ChoiRepr`` 或 ``KrausRepr`` 或 ``StinespringRepr`` 或 ``paddle.Tensor``。
:raises TypeError: "dim_io" 必须是 "int" 或者 "tuple"。
:warning: 输入的 ``channel_repr`` 不是choi表示, 已被转换成 ``ChoiRepr``。
:return: 返回菱形范数
:rtype: float
.. py:function:: channel_convert(original_channel, target, tol)
将给定的信道转换成目标形式
:param original_channel: 输入信道
:type original_channel: Union[ChoiRepr, KrausRepr, StinespringRepr]
:param target: 目标形式,应为 ``Choi``, ``Kraus`` 或 ``Stinespring``
:type target: str
:param tol: 容错误差
:type tol: float, optional
:raises ValueError: 不支持的信道表示形式,应为 ``Choi``, ``Kraus`` 或 ``Stinespring``.
.. note::
choi变为kraus目前因为eigh的精度会存在1e-6的误差
:raises NotImplementedError: 不支持输入数据类型的信道转换
:return: 返回目标形式的信道
:rtype: Union[ChoiRepr, KrausRepr, StinespringRepr]
.. py:function:: kraus_oper_random(num_qubits: int, num_oper: int)
随机输出一组描述量子信道的Kraus算符
:param num_qubits: 信道对应的量子比特数量
:type num_qubits: int
:param num_oper: Kraus算符的数量
:type num_oper: int
:return: 一组Kraus算符
:rtype: list
\ No newline at end of file
paddle\_quantum.qml
=============================
量子机器学习模块。
.. rubric:: Submodules
.. toctree::
:maxdepth: 4
paddle_quantum.qml.vsql
paddle\_quantum.qml.vsql
==============================================
VSQL 模型。
.. py:function:: norm_image(images, num_qubits)
对输入的图片进行归一化。先将图片展开为向量,再进行归一化。
:param images: 输入的图片。
:type images: List[np.ndarray]
:param num_qubits: 量子比特的数量,决定了归一化向量的维度。
:type num_qubits: int
:return: 返回归一化之后的向量,它是由 ``paddle.Tensor`` 组成的列表。
:rtype: List[paddle.Tensor]
.. py:function:: data_loading(num_qubits, mode, classes, num_data)
加载 MNIST 数据集,其中只包含指定的数据。
:param num_qubits: 量子比特的数量,决定了归一化向量的维度。
:type num_qubits: int
:param mode: 指定要加载的数据集,为 ``'train'`` 或 ``'test'`` 。
- ``'train'`` :表示加载训练集。
- ``'test'`` :表示加载测试集。
:type mode: str
:param classes: 要加载的数据的标签。对应标签的数据会被加载。
:type classes: list
:param num_data: 要加载的数据的数量。默认为 ``None`` ,加载所有数据。
:type num_data: Optional[int]
:return: 返回加载的数据集,其组成为 ``(images, labels)`` 。
:rtype: Tuple[List[np.ndarray], List[int]]
.. py:function:: observable(start_idx, num_shadow)
生成测量量子态所需要的哈密顿量。
:param start_idx: 要测量的量子比特的起始索引。
:type start_idx: int
:param num_shadow: 影子电路所包含的量子比特的数量。
:type num_shadow: int
:return: 返回生成的哈密顿量。
:rtype: paddle_quantum.Hamiltonian
.. py:class:: VSQL(num_qubits, num_shadow, num_classes, depth)
基类::py:class:`paddle.nn.Layer`
变分影子量子学习(variational shadow quantum learning, VSQL)模型的实现。具体细节可以参考:https://ojs.aaai.org/index.php/AAAI/article/view/17016 。
:param num_qubits: 量子电路所包含的量子比特的数量。
:type num_qubits: int
:param num_shadow: 影子电路所包含的量子比特的数量。
:type num_shadow: int
:param num_classes: 模型所要分类的类别的数量。
:type num_classes: int
:param depth: 量子电路的深度,默认为 ``1`` 。
:type depth: Optional[int]
.. py:method:: forward(batch_input)
:param batch_input: 模型的输入,其形状为 :math:`(\text{batch_size}, 2^{\text{num_qubits}})` 。
:type batch_input: List[paddle.Tensor]
:return: 返回模型的输出,其形状为 :math:`(\text{batch_size}, \text{num_classes})` 。
:rtype: paddle.Tensor
.. py:function:: train(num_qubits, num_shadow, depth, batch_size, epoch, learning_rate, classes, num_train, num_test)
训练 VSQL 模型。
:param num_qubits: 量子电路所包含的量子比特的数量。
:type num_qubits: int
:param num_shadow: 影子电路所包含的量子比特的数量。
:type num_shadow: int
:param depth: 量子电路的深度,默认为 ``1`` 。
:type depth: Optional[int]
:param batch_size: 数据的批大小,默认为 ``16`` 。
:type batch_size: Optional[int]
:param epoch: 训练的轮数,默认为 ``10`` 。
:type epoch: Optional[int]
:param learning_rate: 更新参数的学习率,默认为 ``0.01`` 。
:type learning_rate: Optional[float]
:param classes: 要预测的手写数字的类别。默认为 ``None`` ,即预测所有的类别。
:type classes: Optional[list]
:param num_train: 训练集的数据量。默认为 ``None`` ,即使用所有的训练数据。
:type num_train: Optional[int]
:param num_test: 测试集的数据量。默认为 ``None`` ,即使用所有的训练数据。
:type num_test: Optional[int]
paddle\_quantum.qpp.angles
===============================
量子相位处理相关工具函数包
.. py:function:: qpp_angle_finder(P, Q)
对一个劳伦对 ``P``, ``Q``,找到相应的角度集合
:param P: 一个劳伦多项式
:type P: Laurent
:param Q: 一个劳伦多项式
:type Q: Laurent
:return:
包含如下元素:
-list_theta: :math:`R_Y` 门对应的角度
-list_phi: :math:`R_Z` 门对应的角度
:rtype: Tuple[List[float], List[float]]
.. py:function:: qpp_angle_approximator(P, Q)
对一个劳伦对 ``P``, ``Q``,估计相应的角度集合
:param P: 一个劳伦多项式
:type P: Laurent
:param Q: 一个劳伦多项式
:type Q: Laurent
:return:
包含如下元素:
-list_theta: :math:`R_Y` 门对应的角度
-list_phi: :math:`R_Z` 门对应的角度
:rtype: Tuple[List[float], List[float]]
.. note::
与 `yzzyz_angle_finder` 不同的是, `yzzyz_angle_approximator` 假定唯一的误差来源是精度误差(而这一般来讲是不正确的)。
.. py:function:: update_angle(coef)
通过 `coef` 从 ``P`` 和 ``Q`` 中计算角度
:param coef: ``P`` 和 ``Q`` 中的第一项和最后一项
:type coef: List[complex]
:return: 角度 `theta` 和 `phi`
:rtype: Tuple[float, float]
:raises ValueError: 参数错误:检查这四个参数{[p_d, p_nd, q_d, q_nd]}
.. py:function:: update_polynomial(P, Q, theta, phi, verify)
通过 `theta` , `phi` 更新 ``P`` , ``Q``
:param P: 一个劳伦多项式
:type P: Laurent
:param Q: 一个劳伦多项式
:type Q: Laurent
:param theta: 一个参数
:type theta: float
:param phi: 一个参数
:type phi: float
:param verify: 验证计算是否正确,默认值为True
:type verify: Optional[bool] = True
:return: 更新后的 ``P``, ``Q``
:rtype: Tuple[List[float], List[float]]
.. py:function:: condition_test(P, Q)
检查 ``P``, ``Q`` 是否满足:
- deg(`P`) = deg(`Q`)
- ``P``, ``Q`` 是否具有相同宇称
- :math:`PP^* + QQ^* = 1`
:param P: 一个劳伦多项式
:type P: Laurent
:param Q: 一个劳伦多项式
:type Q: Laurent
:raises ValueError: PP* + QQ* != 1: 检查你的代码
.. py:function:: yz_decomposition(U)
返回U的yz分解
:param U: 单比特幺正变换
:type U: np.ndarray
:return: `alpha`, `theta`, `phi` 使得 :math:`U[0, 0] = \alpha R_Y(\theta) R_Z(\phi) [0, 0]`
:rtype: Tuple[complex, float, float]
\ No newline at end of file
paddle\_quantum.qpp.laurent
===============================
劳伦类的定义和它的函数
.. py:class:: Laurent
基类: :py:class:`object`
为劳伦多项式定义的类,定义为 :math:`P:\mathbb{C}[X, X^{-1}] \to \mathbb{C} :x \mapsto \sum_{j = -L}^{L} p_j X^j`
:param coef: 劳伦多项式系数的列表,排列为 :math:`\{p_{-L}, ..., p_{-1}, p_0, p_1, ..., p_L\}`
:type coef: np.ndarray
.. py:method:: __call__(X)
计算P(X)的值
:param X: 输入X
:type X: Union[int, float, complex]
:return: P(X)
:rtype: complex
.. py:property:: coef()
以上升顺序给出多项式的系数序列
.. py:property:: conj()
给出多项式的共轭
.. py:property:: roots()
给出多项式根的列表
.. py:property:: norm()
给出多项式系数的绝对值平方之和
.. py:property:: max_norm()
给出多项式的系数的绝对值的最大值
.. py:property:: parity()
给出多项式的宇称
.. py:method:: __copy__()
复制劳伦多项式
:return: 复制后的多项式
:rtype: Laurent
.. py:method:: __add__(other)
劳伦多项式的相加
:param other: 一个标量或一个劳伦多项式 :math:`Q(x) = \sum_{j = -L}^{L} q_{j} X^j`
:type other: Any
:raises TypeError: 不支持劳伦多项式和others的相加
.. py:method:: __mul__(other)
劳伦多项式的相乘
:param other: 一个标量或一个劳伦多项式 :math:`Q(x) = \sum_{j = -L}^{L} q_{j} X^j`
:type other: Any
:raises TypeError: 不支持劳伦多项式和others的相乘
.. py:method:: __sub__(other)
劳伦多项式的相减
:param other: 一个标量或一个劳伦多项式 :math:`Q(x) = \sum_{j = -L}^{L} q_{j} X^j`
:type other: Any
.. py:method:: __eq__(other)
劳伦多项式的相等
:param other: 一个标量或一个劳伦多项式 :math:`Q(x) = \sum_{j = -L}^{L} q_{j} X^j`
:type other: Any
:raises TypeError: 不支持劳伦多项式和 ``others`` 的相等
.. py:method:: __str__()
打印劳伦多项式
.. py:method:: is_parity(p)
检验劳伦多项式是否有确定宇称
:param p: 宇称
:type p: int
:return:
包含以下元素:
-宇称是否是 ``p mod 2``
-如果不是,返回破坏该宇称的绝对值最大的系数
-如果不是,返回破坏该宇称的绝对值最小的系数
:rtype: Tuple[bool, complex]
.. py:function:: revise_tol(t)
回顾 ``TOL`` 的值
:param t: TOL的值
:type t: float
.. py:function:: ascending_coef(coef)
通过 ``coef`` 从 ``P`` 和 ``Q`` 中计算角度
:param coef: 排列成 :math:`\{ p_0, ..., p_L, p_{-L}, ..., p_{-1} \}` 的系数列表
:type coef: np.ndarray
:return: 排列成 :math:`\{ p_{-L}, ..., p_{-1}, p_0, p_1, ..., p_L \}` 的系数列表
:rtype: np.ndarray
.. py:function:: remove_abs_error(data, tol)
移除数据中的错误
:param data: 数据数组
:type data: np.ndarray
:param tol: 容错率
:type tol: Optional[float] = None
:return: 除错后的数据
:rtype: np.ndarray
.. py:function:: random_laurent_poly(deg, parity, is_real)
随机生成一个劳伦多项式
:param deg: 该多项式的度数
:type deg: int
:param parity: 该多项式的宇称,默认为 ``none``
:type parity: Optional[int] = None
:param is_real: 该多项式系数是否是实数,默认为 ``false``
:type is_real: Optional[bool] = False
:return: 一个模小于等于1的劳伦多项式
:rtype: Laurent
.. py:function:: sqrt_generation(A)
生成劳伦多项式 :math:`A` 的平方根
:param A: 一个劳伦多项式
:type A: Laurent
:return: 一个模小于等于1的劳伦多项式
:rtype: Laurent
.. py:function:: Q_generation(P)
生成劳伦多项式 :math:`P` 的互补多项式
:param P: 一个宇称为 :math:`deg` ,度数为 :math:`L` 的劳伦多项式
:type P: Laurent
:return: 一个宇称为 :math:`deg` ,度数为 :math:`L` 的劳伦多项式 :math:`Q` ,使得 :math:`PP^* + QQ^* = 1`
:rtype: Laurent
.. py:function:: pair_generation(f)
生成劳伦多项式 :math:`f` 的劳伦对
:param f: 一个实的,偶次的,max_norm小于1的劳伦多项式
:type f: Laurent
:return: 劳伦多项式 :math:`P, Q` 使得 :math:`P = \sqrt{1 + f / 2}, Q = \sqrt{1 - f / 2}`
:rtype: Laurent
.. py:function:: laurent_generator(fn, dx, deg, L)
生成劳伦多项式 :math:`f` 的劳伦对
:param fn: 要近似的函数
:type fn: Callable[[np.ndarray], np.ndarray]
:param dx: 数据点的采样频率
:type dx: float
:param deg: 劳伦多项式的度数
:type deg: int
:param L: 近似宽度的一半
:type L: float
:return: 一个度数为 ``deg`` 的,在区间 :math:`[-L, L]` 内近似`fn` 的劳伦多项式
:rtype: Laurent
.. py:function:: deg_finder(fn, delta, l)
找到一个度数,使得由 ``laurent_generator`` 生成的劳伦多项式具有小于1的max_norm
:param fn: 要近似的函数
:type fn: Callable[[np.ndarray], np.ndarray]
:param delta: 数据点的采样频率,默认值为 :math:`0.00001 \pi`
:type delta: Optional[float] = 0.00001 * np.pi
:param l: 近似宽度的一半,默认值为 :math:`\pi`
:type l: Optional[float] = np.pi
:return: 该近似的度数
:rtype: int
.. py:function:: step_laurent(deg)
生成一个近似阶梯函数的劳伦多项式
:param deg: 输出劳伦多项式的度数(为偶数)
:type deg: int
:return: 一个估计 :math:`f(x) = 0.5` if :math:`x <= 0` else :math:`0` 的劳伦多项式
:rtype: Laurent
.. note::
在哈密顿量能量计算器中使用
.. py:function:: hamiltonian_laurent(t, deg)
生成一个近似哈密顿量演化函数的劳伦多项式
:param t: 演化常数(时间)
:type t: float
:param deg: 输出劳伦多项式的度数(为偶数)
:type deg: int
:return: 一个估计 :math:`e^{it \cos(x)}` 的劳伦多项式
:rtype: Laurent
.. note::
-起源于Jacobi-Anger展开: :math:`y(x) = \sum_n i^n Bessel(n, x) e^{inx}`
-在哈密顿量模拟中使用
.. py:function:: ln_laurent(deg, t)
生成一个近似ln函数的劳伦多项式
:param deg: 劳伦多项式的度数(是4的因子)
:type deg: int
:param t: 归一化常数
:type t: float
:return: 一个估计 :math:`ln(cos(x)^2) / t` 的劳伦多项式
:rtype: Laurent
.. note::
在冯诺依曼熵的估计中使用。
.. py:function:: comb(n, k)
计算nCr(n, k)
:param n: 输入参数
:type n: float
:param k: 输入参数
:type k: int
:return: nCr(n, k)
:rtype: float
.. py:function:: power_laurent(deg, alpha, t)
生成近似幂函数的劳伦多项式
:param deg: 劳伦多项式的度数(是4的因子)
:type deg: int
:param alpha: 幂函数的幂次
:type alpha: int
:param t: 归一化常数
:type t: float
:return: 一个估计 :math:`(cos(x)^2)^{\alpha / 2} / t` 的劳伦多项式
:rtype: Laurent
paddle\_quantum.qpp
============================
量子相位处理模块
.. rubric:: Submodules
.. toctree::
:maxdepth: 4
paddle_quantum.qpp.angles
paddle_quantum.qpp.laurent
paddle_quantum.qpp.utils
\ No newline at end of file
paddle\_quantum.qpp.utils
============================
QPP 线路变换和其他相关工具。更多细节参考论文 https://arxiv.org/abs/2209.14278 中的 Theorem 6。
.. py:function:: qpp_cir(list_theta, list_phi, U) -> Circuit
根据 ``list_theta`` 和 ``list_phi`` 创建量子相位处理器
:param list_theta: :math:`R_Y` 门角度输入。
:type list_theta: Union[np.ndarray, paddle.Tensor]
:param list_phi: :math:`R_Z` 门角度输入。
:type list_phi: Union[np.ndarray, paddle.Tensor]
:param U: 酉矩阵或标量输入。
:type U: Union[np.ndarray, paddle.Tensor, float]
:return: 三角 QSP 的多比特一般化线路。
:rtype: Circuit
.. py:function:: simulation_cir(fn, U, approx=True, deg=50, length=np.pi, step_size=0.00001*np.pi, tol=1e-30)
返回一个模拟 ``fn`` 的 QPP 线路,见论文 https://arxiv.org/abs/2209.14278 中的 Theorem 6。
:param fn: 要模拟的函数。
:type fn: Callable[[np.ndarray], np.ndarray]
:param U: 酉矩阵。
:type U: Union[np.ndarray, paddle.Tensor, float]
:param approx: 是否估算线路角度。默认为 ``True``。
:type approx: : Optional[bool]
:param deg: 模拟的级数。默认为 ``50``。
:type deg: : Optional[int]
:param length: 模拟宽度的一半。默认为 :math:`\pi`。
:type length: Optional[float]
:param step_size: 采样点的频率。默认为 :math:`0.00001 \pi`。
:type step_size: Optional[float]
:param tol: 误差容忍度。默认为 :math:`10^{-30}`,即机械误差。
:type tol: Optional[float]
:return: 模拟 ``fn`` 的 QPP 线路。
:rtype: Circuit
.. py:function:: qps(U, initial_state)
量子相位搜索算法,见论文 https://arxiv.org/abs/2209.14278 中的算法 1 和 2
:param U: 目标酉矩阵。
:type U: Union[np.ndarray, paddle.Tensor]
:param initial_state: 输入量子态。
:type initial_state: Union[np.ndarray, paddle.Tensor, State]
:return: 包含如下元素的 tuple:
- 一个 ``U`` 的本征相位;
- 其相位对应的,存在和 ``initial_state`` 内积不为零的本征态。
:rtype: Tuple[float, State]
.. py:function:: qubitize(block_enc, num_block_qubits)
使用一个额外辅助比特来比特化块编码,来保证子空间不变。更多细节见论文 http://arxiv.org/abs/1610.06546。
:param block_enc: 目标块编码。
:type block_enc: Union[np.ndarray, paddle.Tensor]
:param num_block_qubits: 块编码自身所使用的辅助比特数。
:type num_block_qubits: int
:return: 比特化的 ``block_enc``
:rtype: paddle.Tensor
.. py:function:: purification_block_enc(num_qubits, num_block_qubits):
随机生成一个 :math:`n`-比特密度矩阵的 :math:`(n + m)`-比特的比特化块编码。
:param num_qubits: 量子态比特数 :math:`n`。
:type num_qubits: int
:param num_block_qubits: 块编码的辅助比特数 :math:`m > n`。
:type num_block_qubits: int
:return: 一个 :math:`2^{n + m} \times 2^{n + m}` 的左上角为密度矩阵的酉矩阵
:rtype: paddle.Tensor
...@@ -10,7 +10,7 @@ paddle\_quantum.qsvt.qsp\_utils ...@@ -10,7 +10,7 @@ paddle\_quantum.qsvt.qsp\_utils
:param degree: 多项式的度 :param degree: 多项式的度
:type degree: int :type degree: int
:param odd: 多项式的奇偶性,输入 `True` 则为奇函数, `False` 则为偶函数 :param odd: 多项式的奇偶性,输入 `True` 则为奇函数, `False` 则为偶函数
:type odd: bool :type od: bool
:return: 一个随机生成的多项式 :return: 一个随机生成的多项式
:rtype: Polynomial :rtype: Polynomial
......
...@@ -17,8 +17,7 @@ paddle\_quantum.state.state ...@@ -17,8 +17,7 @@ paddle\_quantum.state.state
:type backend: paddle_quantum.Backend, optional :type backend: paddle_quantum.Backend, optional
:param dtype: 量子态的数据类型。默认为 None,使用全局的默认数据类型。 :param dtype: 量子态的数据类型。默认为 None,使用全局的默认数据类型。
:type dtype: str, optional :type dtype: str, optional
:raises Exception: 所输入的量子态维度不正确。 :raises ValueError: 无法识别后端
:raises NotImplementedError: 所指定的后端必须为量桨已经实现的后端。
.. py:property:: ket() .. py:property:: ket()
...@@ -29,6 +28,56 @@ paddle\_quantum.state.state ...@@ -29,6 +28,56 @@ paddle\_quantum.state.state
得到量子态的行向量形式。 得到量子态的行向量形式。
.. py:method:: normalize()
得到归一化后量子态
:raises NotImplementedError: 当前后端不支持归一化
.. py:method:: evolve(H,t)
得到经过给定哈密顿量演化后的量子态
:param H: 系统哈密顿量
:type H: Union[np.ndarray, paddle.Tensor, Hamiltonian]
:param t: 演化时间
:type t: float
:raises NotImplementedError: 当前后端不支持量子态演化
.. py:method:: kron(other)
得到与给定量子态之间的张量积
:param other: 给定量子态
:type other: State
:return: 返回张量积
:rtype: State
.. py:method:: __matmul__(other)
得到与量子态或张量之间的乘积
:param other: 给定量子态
:type other: State
:raises NotImplementedError: 不支持与该量子态进行乘积计算
:raises ValueError: 无法对两个态向量相乘,请检查使用的后端
:return: 返回量子态的乘积
:rtype: paddle.Tensor
.. py:method:: __rmatmul__(other)
得到与量子态或张量之间的乘积
:param other: 给定量子态
:type other: State
:raises NotImplementedError: 不支持与该量子态进行乘积计算
:raises ValueError: 无法对两个态向量相乘,请检查使用的后端
:return: 返回量子态的乘积
:rtype: paddle.Tensor
.. py:method:: numpy() .. py:method:: numpy()
得到量子态的数据的 numpy 形式。 得到量子态的数据的 numpy 形式。
...@@ -48,8 +97,8 @@ paddle\_quantum.state.state ...@@ -48,8 +97,8 @@ paddle\_quantum.state.state
:type device: str :type device: str
:param blocking: 如果为 False 并且当前 Tensor 处于固定内存上,将会发生主机到设备端的异步拷贝。否则会发生同步拷贝。如果为 None,blocking 会被设置为 True,默认 为False。 :param blocking: 如果为 False 并且当前 Tensor 处于固定内存上,将会发生主机到设备端的异步拷贝。否则会发生同步拷贝。如果为 None,blocking 会被设置为 True,默认 为False。
:type blocking: str :type blocking: str
:return: 返回 NotImplementedError,该函数会在后续实现。 :raises NotImplementedError: 仅支持在态向量与密度矩阵之间进行转换
:rtype: Error :raises NotImplementedError: 不支持在该设备或blocking上进行转换
.. py:method:: clone() .. py:method:: clone()
...@@ -86,3 +135,79 @@ paddle\_quantum.state.state ...@@ -86,3 +135,79 @@ paddle\_quantum.state.state
:raises NotImplementedError: 输入的量子比特下标有误。 :raises NotImplementedError: 输入的量子比特下标有误。
:return: 测量结果。 :return: 测量结果。
:rtype: dict :rtype: dict
.. py:function:: _type_fetch(data)
获取数据的类型
:param data: 输入数据
:type data: Union[np.ndarray, paddle.Tensor, State]
:raises ValueError: 输入量子态不支持所选后端
:raises TypeError: 无法识别输入量子态的数据类型
:return: 返回输入量子态的数据类型
:rtype: str
.. py:function:: _density_to_vector(rho)
将密度矩阵转换为态向量
:param rho: 输入的密度矩阵
:type rho: Union[np.ndarray, paddle.Tensor]
:raises ValueError: 输出量子态可能不为纯态
:return: 返回态向量
:rtype: Union[np.ndarray, paddle.Tensor]
.. py:function:: _type_transform(data, output_type)
将输入量子态转换成目标类型
:param data: 需要转换的数据
:type data: Union[np.ndarray, paddle.Tensor, State]
:param output_type: 目标数据类型
:type output_type: str
:raises ValueError: 输入态不支持转换为目标数据类型
:return: 返回目标数据类型的量子态
:rtype: Union[np.ndarray, paddle.Tensor, State]
.. py:function:: is_state_vector(vec, eps)
检查输入态是否为量子态向量
:param vec: 输入的数据 :math:`x`
:type vec: Union[np.ndarray, paddle.Tensor]
:param eps: 容错率
:type eps: float, optional
:return: 返回是否满足 :math:`x^\dagger x = 1` ,以及量子比特数目或错误信息
:rtype: Tuple[bool, int]
.. note::
错误信息为:
* ``-1`` 如果上述公式不成立
* ``-2`` 如果输入数据维度不为2的幂
* ``-3`` 如果输入数据不为向量
.. py:function:: is_density_matrix(rho, eps)
检查输入数据是否为量子态的密度矩阵
:param rho: 输入的数据 ``rho``
:type rho: Union[np.ndarray, paddle.Tensor]
:param eps: 容错率
:type eps: float, optional
:return: 返回输入数据 ``rho`` 是否为迹为1的PSD矩阵,以及量子比特数目或错误信息
:rtype: Tuple[bool, int]
.. note::
错误信息为:
* ``-1`` 如果 ``rho`` 不为PSD矩阵
* ``-2`` 如果 ``rho`` 的迹不为1
* ``-3`` 如果 ``rho`` 的维度不为2的幂
* ``-4`` 如果 ``rho`` 不为一个方阵
...@@ -28,7 +28,13 @@ ...@@ -28,7 +28,13 @@
"source": [ "source": [
"## 量桨连接量易伏\n", "## 量桨连接量易伏\n",
"\n", "\n",
"目前量桨已经支持连接量易伏,只需要使用 `paddle_quantum.set_backend('quleaf')` 即可将量桨的后端设置为量易伏。除此之外,我们还需要设置量易伏的模拟方式。如果使用云端算力,则还需要输入 token。因此,完整的设置代码如下:" "### 环境设置\n",
"\n",
"如果要使用量桨连接量易伏上的真实量子计算机,则需要保证所安装的 qcompute 的版本在 3.0.0 以上,使用 `pip install -U qcompute` 来安装最新版本的 qcompute。\n",
"\n",
"其中,qcompute 对 protobuf 的版本要求是 4.21.1,而飞桨对 protobuf 的版本要求为 3.1.0 到3.20.0。因此,需要通过设置环境变量来使飞桨可以兼容更高版本的 protobuf。\n",
"\n",
"> 因为飞桨对 protobuf 的版本要求为 3.1.0 到3.20.0,所以所有的 Python 程序都需要先设置环境变量才能正常使用量桨。因此,如果不使用量易伏的话,可以通过 `pip install protobuf==3.20.0` 来正常使用量桨而无需额外设置环境变量。"
] ]
}, },
{ {
...@@ -36,6 +42,24 @@ ...@@ -36,6 +42,24 @@
"execution_count": 1, "execution_count": 1,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [
"# 设置环境变量使飞桨可以兼容更高版本的 protobuf\n",
"import os\n",
"os.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"接下来,只需要使用 `paddle_quantum.set_backend('quleaf')` 即可将量桨的后端设置为量易伏。除此之外,我们还需要设置量易伏的模拟方式。如果使用云端算力,则还需要输入 token。因此,完整的设置代码如下:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [ "source": [
"import paddle_quantum\n", "import paddle_quantum\n",
"from QCompute import BackendName\n", "from QCompute import BackendName\n",
...@@ -79,7 +103,7 @@ ...@@ -79,7 +103,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 3,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -109,21 +133,21 @@ ...@@ -109,21 +133,21 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 5,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"The iter is 0, loss is -0.32104.\n", "The iter is 0, loss is -0.36935.\n",
"The iter is 10, loss is -0.49239.\n", "The iter is 10, loss is -0.51383.\n",
"The iter is 20, loss is -0.80956.\n", "The iter is 20, loss is -0.74604.\n",
"The iter is 30, loss is -1.07812.\n", "The iter is 30, loss is -1.02815.\n",
"The iter is 40, loss is -1.09850.\n", "The iter is 40, loss is -1.07233.\n",
"The iter is 50, loss is -1.13334.\n", "The iter is 50, loss is -1.09937.\n",
"The iter is 60, loss is -1.13445.\n", "The iter is 60, loss is -1.11564.\n",
"The iter is 70, loss is -1.13492.\n", "The iter is 70, loss is -1.11459.\n",
"The theoretical value is -1.137283834485513.\n" "The theoretical value is -1.137283834485513.\n"
] ]
} }
...@@ -167,7 +191,7 @@ ...@@ -167,7 +191,7 @@
"loss_func = paddle_quantum.loss.ExpecVal(hamiltonian, shots=10000)\n", "loss_func = paddle_quantum.loss.ExpecVal(hamiltonian, shots=10000)\n",
"# 进行迭代训练\n", "# 进行迭代训练\n",
"num_itr = 80\n", "num_itr = 80\n",
"for itr in range(0, num_itr):\n", "for itr in range(num_itr):\n",
" state = circuit(init_state)\n", " state = circuit(init_state)\n",
" loss = loss_func(state)\n", " loss = loss_func(state)\n",
" loss.backward()\n", " loss.backward()\n",
...@@ -190,7 +214,7 @@ ...@@ -190,7 +214,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3.8.0 ('paddle-quantum-dev')", "display_name": "Python 3.8.0 ('temp')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -209,7 +233,7 @@ ...@@ -209,7 +233,7 @@
"orig_nbformat": 4, "orig_nbformat": 4,
"vscode": { "vscode": {
"interpreter": { "interpreter": {
"hash": "9043b12ec77a531919bc05f05830335d23baf822720cbea14b03018197d26545" "hash": "73730e2524c172926674de584a45f4a289689f765fd1f4813f545a2476542e53"
} }
} }
}, },
......
...@@ -28,7 +28,13 @@ ...@@ -28,7 +28,13 @@
"source": [ "source": [
"## Paddle Quantum calls QuLeaf\n", "## Paddle Quantum calls QuLeaf\n",
"\n", "\n",
"In the paddle quantum, we already support the backend implementation of QuLeaf. Just use `paddle_quantum.set_backend('quleaf')` to set the backend of the quantum paddle to QuLeaf. In addition to that, we need to set the simulation method of the QuLeaf. If we use cloud computation power, we also need to enter the token, so the complete setup code is as follows." "### Environment settings\n",
"\n",
"If you want to use paddle-quantum to connect to the real quantum computer via QuLeaf, you need to ensure that the installed version of qcompute is above 3.0.0. You can use `pip install -U qcompute` to install the latest version of qcompute.\n",
"\n",
"In particular, qcompute requires protobuf version 4.21.1, while paddlepaddle requires protobuf versions 3.1.0 to 3.20.0. Therefore, you need to set environment variables to make paddlepaddle compatible with higher versions of protobuf.\n",
"\n",
"> Because paddlepaddle requires protobuf versions 3.1.0 through 3.20.0, all your python programs need to set environment variables so that they can import the paddlepaddle successfully. Therefore, if you don't need to use the qcompute, you can use `pip install protobuf==3.20.0` to ley you import the paddlepaddle without setting environment variable."
] ]
}, },
{ {
...@@ -36,6 +42,24 @@ ...@@ -36,6 +42,24 @@
"execution_count": 1, "execution_count": 1,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [
"# Set the environment variable to make paddlepaddle compatible with the higher version of protobuf\n",
"import os\n",
"os.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, just use `paddle_quantum.set_backend('quleaf')` to set the backend of the quantum paddle to QuLeaf. In addition to that, we need to set the simulation method of the QuLeaf. If we use cloud computation power, we also need to enter the token, so the complete setup code is as follows."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [ "source": [
"import paddle_quantum\n", "import paddle_quantum\n",
"from QCompute import BackendName\n", "from QCompute import BackendName\n",
...@@ -79,7 +103,7 @@ ...@@ -79,7 +103,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 3,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -109,21 +133,21 @@ ...@@ -109,21 +133,21 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 5,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"The iter is 0, loss is 0.15198.\n", "The iter is 0, loss is -0.02081.\n",
"The iter is 10, loss is -0.76691.\n", "The iter is 10, loss is -0.54088.\n",
"The iter is 20, loss is -0.91600.\n", "The iter is 20, loss is -0.83405.\n",
"The iter is 30, loss is -0.97056.\n", "The iter is 30, loss is -1.01126.\n",
"The iter is 40, loss is -1.05518.\n", "The iter is 40, loss is -1.10169.\n",
"The iter is 50, loss is -1.13397.\n", "The iter is 50, loss is -1.10362.\n",
"The iter is 60, loss is -1.12326.\n", "The iter is 60, loss is -1.11572.\n",
"The iter is 70, loss is -1.13693.\n", "The iter is 70, loss is -1.11396.\n",
"The theoretical value is -1.137283834485513.\n" "The theoretical value is -1.137283834485513.\n"
] ]
} }
...@@ -167,7 +191,7 @@ ...@@ -167,7 +191,7 @@
"loss_func = paddle_quantum.loss.ExpecVal(hamiltonian, shots=10000)\n", "loss_func = paddle_quantum.loss.ExpecVal(hamiltonian, shots=10000)\n",
"# iterative training\n", "# iterative training\n",
"num_itr = 80\n", "num_itr = 80\n",
"for itr in range(0, num_itr):\n", "for itr in range(num_itr):\n",
" state = circuit(init_state)\n", " state = circuit(init_state)\n",
" loss = loss_func(state)\n", " loss = loss_func(state)\n",
" loss.backward()\n", " loss.backward()\n",
...@@ -190,7 +214,7 @@ ...@@ -190,7 +214,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3.8.0 ('paddle-quantum-dev')", "display_name": "Python 3.8.0 ('temp')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -209,7 +233,7 @@ ...@@ -209,7 +233,7 @@
"orig_nbformat": 4, "orig_nbformat": 4,
"vscode": { "vscode": {
"interpreter": { "interpreter": {
"hash": "9043b12ec77a531919bc05f05830335d23baf822720cbea14b03018197d26545" "hash": "73730e2524c172926674de584a45f4a289689f765fd1f4813f545a2476542e53"
} }
} }
}, },
......
...@@ -28,7 +28,6 @@ from .hamiltonian import Hamiltonian ...@@ -28,7 +28,6 @@ from .hamiltonian import Hamiltonian
from . import ansatz from . import ansatz
from . import channel from . import channel
from . import gate from . import gate
from . import linalg
from . import locc from . import locc
from . import loss from . import loss
from . import mbqc from . import mbqc
...@@ -41,9 +40,10 @@ from . import gradtool ...@@ -41,9 +40,10 @@ from . import gradtool
from . import hamiltonian from . import hamiltonian
from . import linalg from . import linalg
from . import qinfo from . import qinfo
from . import qml
from . import shadow from . import shadow
from . import trotter from . import trotter
from . import visual from . import visual
name = 'paddle_quantum' name = 'paddle_quantum'
__version__ = '2.2.1' __version__ = '2.2.2'
...@@ -20,7 +20,7 @@ The source file of the Circuit class. ...@@ -20,7 +20,7 @@ The source file of the Circuit class.
import warnings import warnings
import paddle import paddle
from .container import Sequential from .container import Sequential
from ..gate import Gate, H, S, T, X, Y, Z, P, RX, RY, RZ, U3 from ..gate import Gate, H, S, Sdg, T, Tdg, X, Y, Z, P, RX, RY, RZ, U3
from ..gate import CNOT, CX, CY, CZ, SWAP from ..gate import CNOT, CX, CY, CZ, SWAP
from ..gate import CP, CRX, CRY, CRZ, CU, RXX, RYY, RZZ from ..gate import CP, CRX, CRY, CRZ, CU, RXX, RYY, RZZ
from ..gate import MS, CSWAP, Toffoli from ..gate import MS, CSWAP, Toffoli
...@@ -31,7 +31,7 @@ from ..gate import RealBlockLayer, RealEntangledLayer, ComplexBlockLayer, Comple ...@@ -31,7 +31,7 @@ from ..gate import RealBlockLayer, RealEntangledLayer, ComplexBlockLayer, Comple
from ..gate import QAOALayer from ..gate import QAOALayer
from ..gate import AmplitudeEncoding from ..gate import AmplitudeEncoding
from ..channel import BitFlip, PhaseFlip, BitPhaseFlip, AmplitudeDamping, GeneralizedAmplitudeDamping, PhaseDamping from ..channel import BitFlip, PhaseFlip, BitPhaseFlip, AmplitudeDamping, GeneralizedAmplitudeDamping, PhaseDamping
from ..channel import Depolarizing, PauliChannel, ResetChannel, ThermalRelaxation, KrausRepr from ..channel import Depolarizing, PauliChannel, ResetChannel, ThermalRelaxation, MixedUnitaryChannel, KrausRepr
from ..intrinsic import _get_float_dtype from ..intrinsic import _get_float_dtype
from ..state import zero_state from ..state import zero_state
from ..operator import Collapse from ..operator import Collapse
...@@ -39,6 +39,9 @@ from typing import Union, Iterable, Optional, Dict, List, Tuple ...@@ -39,6 +39,9 @@ from typing import Union, Iterable, Optional, Dict, List, Tuple
from paddle_quantum import State, get_backend, get_dtype, Backend from paddle_quantum import State, get_backend, get_dtype, Backend
from math import pi from math import pi
import numpy as np import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from ..gate.functional.visual import _circuit_plot
class Circuit(Sequential): class Circuit(Sequential):
...@@ -47,14 +50,14 @@ class Circuit(Sequential): ...@@ -47,14 +50,14 @@ class Circuit(Sequential):
Args: Args:
num_qubits: Number of qubits. Defaults to None. num_qubits: Number of qubits. Defaults to None.
""" """
def __init__(self, num_qubits: Optional[int] = None): def __init__(self, num_qubits: Optional[int] = None):
super().__init__() super().__init__()
self.__num_qubits = num_qubits self.__num_qubits = num_qubits
# whether the circuit is a dynamic quantum circuit # whether the circuit is a dynamic quantum circuit
self.__isdynamic = True if num_qubits is None else False self.__isdynamic = num_qubits is None
# alias for ccx # alias for ccx
self.toffoli = self.ccx self.toffoli = self.ccx
...@@ -69,20 +72,20 @@ class Circuit(Sequential): ...@@ -69,20 +72,20 @@ class Circuit(Sequential):
r"""Whether the circuit is dynamic r"""Whether the circuit is dynamic
""" """
return self.__dynamic return self.__dynamic
@num_qubits.setter @num_qubits.setter
def num_qubits(self, value: int) -> None: def num_qubits(self, value: int) -> None:
assert isinstance(value, int) assert isinstance(value, int)
self.__num_qubits = value self.__num_qubits = value
@property @property
def param(self) -> paddle.Tensor: def param(self) -> paddle.Tensor:
r"""Flattened parameters in the circuit. r"""Flattened parameters in the circuit.
""" """
if len(self.parameters()) == 0: if len(self.parameters()) == 0:
return [] return []
return paddle.concat([paddle.flatten(param) for param in self.parameters()]) return paddle.concat([paddle.flatten(param) for param in self.parameters()])
@property @property
def grad(self) -> np.ndarray: def grad(self) -> np.ndarray:
r"""Gradients with respect to the flattened parameters. r"""Gradients with respect to the flattened parameters.
...@@ -93,14 +96,7 @@ class Circuit(Sequential): ...@@ -93,14 +96,7 @@ class Circuit(Sequential):
' otherwise check where the gradient chain is broken' ' otherwise check where the gradient chain is broken'
grad_list.append(paddle.flatten(param.grad)) grad_list.append(paddle.flatten(param.grad))
return paddle.concat(grad_list).numpy() return paddle.concat(grad_list).numpy()
@property
def depth(self) -> int:
r"""(current) Depth of this Circuit
"""
qubit_depth = [len(qubit_gates) for qubit_gates in self.qubit_history]
return max(qubit_depth)
def update_param(self, theta: Union[paddle.Tensor, np.ndarray, float], idx: int = None) -> None: def update_param(self, theta: Union[paddle.Tensor, np.ndarray, float], idx: int = None) -> None:
r"""Replace parameters of all/one layer(s) by ``theta``. r"""Replace parameters of all/one layer(s) by ``theta``.
...@@ -111,14 +107,15 @@ class Circuit(Sequential): ...@@ -111,14 +107,15 @@ class Circuit(Sequential):
if not isinstance(theta, paddle.Tensor): if not isinstance(theta, paddle.Tensor):
theta = paddle.to_tensor(theta, dtype='float32') theta = paddle.to_tensor(theta, dtype='float32')
theta = paddle.flatten(theta) theta = paddle.flatten(theta)
backend_dtype = _get_float_dtype(get_dtype()) backend_dtype = _get_float_dtype(get_dtype())
if backend_dtype != 'float32': if backend_dtype != 'float32':
warnings.warn( warnings.warn(
f"\ndtype of parameters will be float32 instead of {backend_dtype}", UserWarning) f"\ndtype of parameters will be float32 instead of {backend_dtype}", UserWarning)
if idx is None: if idx is None:
assert self.param.shape == theta.shape, "the shape of input parameters is not correct" assert self.param.shape == theta.shape, \
f"the shape of input parameters is not correct: expect {self.param.shape}, received {theta.shape}"
for layer in self.sublayers(): for layer in self.sublayers():
for name, _ in layer.named_parameters(): for name, _ in layer.named_parameters():
param = getattr(layer, name) param = getattr(layer, name)
...@@ -135,11 +132,12 @@ class Circuit(Sequential): ...@@ -135,11 +132,12 @@ class Circuit(Sequential):
return return
theta = theta[num_param:] theta = theta[num_param:]
elif isinstance(idx, int): elif isinstance(idx, int):
assert idx < len(self.sublayers()), "the index is out of range, expect below " + str(len(self.sublayers())) assert idx < len(self.sublayers()), f"the index is out of range, expect below {len(self.sublayers())}"
layer = self.sublayers()[idx] layer = self.sublayers()[idx]
assert theta.shape == paddle.concat([paddle.flatten(param) for param in layer.parameters()]).shape, \ assert theta.shape == paddle.concat([paddle.flatten(param) for param in layer.parameters()]).shape, \
"the shape of input parameters is not correct," "the shape of input parameters is not correct,"
for name, _ in layer.named_parameters(): for name, _ in layer.named_parameters():
param = getattr(layer, name) param = getattr(layer, name)
num_param = int(paddle.numel(param)) num_param = int(paddle.numel(param))
...@@ -156,20 +154,20 @@ class Circuit(Sequential): ...@@ -156,20 +154,20 @@ class Circuit(Sequential):
theta = theta[num_param:] theta = theta[num_param:]
else: else:
raise ValueError("idx must be an integer or None") raise ValueError("idx must be an integer or None")
def transfer_static(self) -> None: def transfer_static(self) -> None:
r""" set ``stop_gradient`` of all parameters of the circuit as ``True`` r"""
set ``stop_gradient`` of all parameters of the circuit as ``True``
""" """
for layer in self.sublayers(): for layer in self.sublayers():
for name, _ in layer.named_parameters(): for name, _ in layer.named_parameters():
param = getattr(layer, name) param = getattr(layer, name)
param.stop_gradient = True param.stop_gradient = True
setattr(layer, 'theta', param) setattr(layer, 'theta', param)
def randomize_param(self, low: float = 0, high: Optional[float] = 2 * pi) -> None: def randomize_param(self, low: float = 0, high: Optional[float] = 2 * pi) -> None:
r"""Randomize parameters of the circuit in a range from low to high. r"""Randomize parameters of the circuit in a range from low to high.
Args: Args:
low: Lower bound. low: Lower bound.
high: Upper bound. high: Upper bound.
...@@ -187,7 +185,7 @@ class Circuit(Sequential): ...@@ -187,7 +185,7 @@ class Circuit(Sequential):
def __num_qubits_update(self, qubits_idx: Union[Iterable[int], int, str]) -> None: def __num_qubits_update(self, qubits_idx: Union[Iterable[int], int, str]) -> None:
r"""Update ``self.num_qubits`` according to ``qubits_idx``, or report error. r"""Update ``self.num_qubits`` according to ``qubits_idx``, or report error.
Args: Args:
qubits_idx: Input qubit indices of a quantum gate. qubits_idx: Input qubit indices of a quantum gate.
""" """
...@@ -256,6 +254,30 @@ class Circuit(Sequential): ...@@ -256,6 +254,30 @@ class Circuit(Sequential):
self.append( self.append(
S(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) S(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def sdg(
self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1
) -> None:
r"""Add single-qubit S dagger (S inverse) gates.
The matrix form of such a gate is:
.. math::
S^\dagger =
\begin{bmatrix}
1&0\\
0&-i
\end{bmatrix}
Args:
qubits_idx: Indices of the qubits on which the gates are applied. Defaults to ``'full'``.
num_qubits: Total number of qubits. Defaults to ``None``.
depth: Number of layers. Defaults to ``1``.
"""
self.__num_qubits_update(qubits_idx)
self.append(
Sdg(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def t( def t(
self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1
) -> None: ) -> None:
...@@ -279,6 +301,30 @@ class Circuit(Sequential): ...@@ -279,6 +301,30 @@ class Circuit(Sequential):
self.append( self.append(
T(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) T(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def tdg(
self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1
) -> None:
r"""Add single-qubit T dagger (T inverse) gates.
The matrix form of such a gate is:
.. math::
T^\dagger =
\begin{bmatrix}
1&0\\
0&e^{-\frac{i\pi}{4}}
\end{bmatrix}
Args:
qubits_idx: Indices of the qubits on which the gates are applied. Defaults to ``'full'``.
num_qubits: Total number of qubits. Defaults to None.
depth: Number of layers. Defaults to 1.
"""
self.__num_qubits_update(qubits_idx)
self.append(
Tdg(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def x( def x(
self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1
) -> None: ) -> None:
...@@ -991,7 +1037,8 @@ class Circuit(Sequential): ...@@ -991,7 +1037,8 @@ class Circuit(Sequential):
def oracle( def oracle(
self, oracle: paddle.tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int], self, oracle: paddle.tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int],
num_qubits: int = None, depth: int = 1, gate_name: str = 'O' num_qubits: int = None, depth: int = 1,
gate_name: Optional[str] = 'O', latex_name: Optional[str] = None, plot_width: Optional[float] = None
) -> None: ) -> None:
"""Add an oracle gate. """Add an oracle gate.
...@@ -1000,15 +1047,21 @@ class Circuit(Sequential): ...@@ -1000,15 +1047,21 @@ class Circuit(Sequential):
qubits_idx: Indices of the qubits on which the gates are applied. qubits_idx: Indices of the qubits on which the gates are applied.
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
gate_name: name of this oracle gate_name: name of this oracle.
latex_name: latex name of this oracle, default to be the gate name.
plot_width: width of this gate in circuit plot, default to be proportional with the gate name.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
gate_info = {'gatename': gate_name,
'texname': f"${gate_name}$" if latex_name is None else latex_name,
'plot_width': 0.6 * len(gate_name) if plot_width is None else plot_width}
self.append(Oracle(oracle, qubits_idx, self.append(Oracle(oracle, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits, depth, gate_name)) self.num_qubits if num_qubits is None else num_qubits, depth, gate_info))
def control_oracle( def control_oracle(
self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int]], self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int]],
num_qubits: int = None, depth: int = 1, gate_name: str = 'cO' num_qubits: int = None, depth: int = 1,
gate_name: Optional[str] = 'O', latex_name: Optional[str] = None, plot_width: Optional[float] = None
) -> None: ) -> None:
"""Add a controlled oracle gate. """Add a controlled oracle gate.
...@@ -1017,11 +1070,16 @@ class Circuit(Sequential): ...@@ -1017,11 +1070,16 @@ class Circuit(Sequential):
qubits_idx: Indices of the qubits on which the gates are applied. qubits_idx: Indices of the qubits on which the gates are applied.
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
gate_name: name of this oracle gate_name: name of this oracle.
latex_name: latex name of this oracle, default to be the gate name.
plot_width: width of this gate in circuit plot, default to be proportional with the gate name.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
gate_info = {'gatename': f"c{gate_name}",
'texname': f"${gate_name}$" if latex_name is None else latex_name,
'plot_width': 0.6 * len(gate_name) if plot_width is None else plot_width}
self.append(ControlOracle(oracle, qubits_idx, self.append(ControlOracle(oracle, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits, depth, gate_name)) self.num_qubits if num_qubits is None else num_qubits, depth, gate_info))
def collapse(self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None, def collapse(self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None,
desired_result: Union[int, str] = None, if_print: bool = False, desired_result: Union[int, str] = None, if_print: bool = False,
...@@ -1037,12 +1095,12 @@ class Circuit(Sequential): ...@@ -1037,12 +1095,12 @@ class Circuit(Sequential):
Raises: Raises:
NotImplementedError: If the basis of measurement is not z. Other bases will be implemented in future. NotImplementedError: If the basis of measurement is not z. Other bases will be implemented in future.
TypeError: cannot get probability of state when the backend is unitary_matrix. TypeError: cannot get probability of state when the backend is unitary_matrix.
Note: Note:
When desired_result is `None`, Collapse does not support gradient calculation When desired_result is `None`, Collapse does not support gradient calculation
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(Collapse(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, self.append(Collapse(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
desired_result, if_print, measure_basis)) desired_result, if_print, measure_basis))
def superposition_layer( def superposition_layer(
...@@ -1074,7 +1132,7 @@ class Circuit(Sequential): ...@@ -1074,7 +1132,7 @@ class Circuit(Sequential):
WeakSuperpositionLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) WeakSuperpositionLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def linear_entangled_layer( def linear_entangled_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
) -> None: ) -> None:
r"""Add linear entangled layers consisting of Ry gates, Rz gates, and CNOT gates. r"""Add linear entangled layers consisting of Ry gates, Rz gates, and CNOT gates.
...@@ -1088,7 +1146,7 @@ class Circuit(Sequential): ...@@ -1088,7 +1146,7 @@ class Circuit(Sequential):
LinearEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) LinearEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def real_entangled_layer( def real_entangled_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
) -> None: ) -> None:
r"""Add strongly entangled layers consisting of Ry gates and CNOT gates. r"""Add strongly entangled layers consisting of Ry gates and CNOT gates.
...@@ -1102,7 +1160,7 @@ class Circuit(Sequential): ...@@ -1102,7 +1160,7 @@ class Circuit(Sequential):
RealEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) RealEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def complex_entangled_layer( def complex_entangled_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
) -> None: ) -> None:
r"""Add strongly entangled layers consisting of single-qubit rotation gates and CNOT gates. r"""Add strongly entangled layers consisting of single-qubit rotation gates and CNOT gates.
...@@ -1116,7 +1174,7 @@ class Circuit(Sequential): ...@@ -1116,7 +1174,7 @@ class Circuit(Sequential):
ComplexEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) ComplexEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def real_block_layer( def real_block_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
) -> None: ) -> None:
r"""Add weakly entangled layers consisting of Ry gates and CNOT gates. r"""Add weakly entangled layers consisting of Ry gates and CNOT gates.
...@@ -1130,7 +1188,7 @@ class Circuit(Sequential): ...@@ -1130,7 +1188,7 @@ class Circuit(Sequential):
RealBlockLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) RealBlockLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def complex_block_layer( def complex_block_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
) -> None: ) -> None:
r"""Add weakly entangled layers consisting of single-qubit rotation gates and CNOT gates. r"""Add weakly entangled layers consisting of single-qubit rotation gates and CNOT gates.
...@@ -1144,7 +1202,7 @@ class Circuit(Sequential): ...@@ -1144,7 +1202,7 @@ class Circuit(Sequential):
ComplexBlockLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) ComplexBlockLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def bit_flip( def bit_flip(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
) -> None: ) -> None:
r"""Add bit flip channels. r"""Add bit flip channels.
...@@ -1154,11 +1212,12 @@ class Circuit(Sequential): ...@@ -1154,11 +1212,12 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(BitFlip(prob, qubits_idx, self.append(BitFlip(prob, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def phase_flip( def phase_flip(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full',
num_qubits: int = None
) -> None: ) -> None:
r"""Add phase flip channels. r"""Add phase flip channels.
...@@ -1168,11 +1227,12 @@ class Circuit(Sequential): ...@@ -1168,11 +1227,12 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(PhaseFlip(prob, qubits_idx, self.append(PhaseFlip(prob, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def bit_phase_flip( def bit_phase_flip(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full',
num_qubits: int = None
) -> None: ) -> None:
r"""Add bit phase flip channels. r"""Add bit phase flip channels.
...@@ -1184,9 +1244,9 @@ class Circuit(Sequential): ...@@ -1184,9 +1244,9 @@ class Circuit(Sequential):
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(BitPhaseFlip(prob, qubits_idx, self.append(BitPhaseFlip(prob, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def amplitude_damping( def amplitude_damping(
self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
) -> None: ) -> None:
r"""Add amplitude damping channels. r"""Add amplitude damping channels.
...@@ -1198,15 +1258,17 @@ class Circuit(Sequential): ...@@ -1198,15 +1258,17 @@ class Circuit(Sequential):
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(AmplitudeDamping(gamma, qubits_idx, self.append(AmplitudeDamping(gamma, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
#TODO: change bug
def generalized_amplitude_damping( def generalized_amplitude_damping(
self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, gamma: Union[paddle.Tensor, float], prob: Union[paddle.Tensor, float],
qubits_idx: Union[Iterable[int], int, str] = 'full',
num_qubits: int = None
) -> None: ) -> None:
r"""Add generalized amplitude damping channels. r"""Add generalized amplitude damping channels.
Args: Args:
gamma: Damping probability. gamma: Damping probability. Its value should be in the range :math:`[0, 1]`.
prob: Excitation probability. Its value should be in the range :math:`[0, 1]`.
qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'.
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
...@@ -1215,7 +1277,8 @@ class Circuit(Sequential): ...@@ -1215,7 +1277,8 @@ class Circuit(Sequential):
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def phase_damping( def phase_damping(
self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full',
num_qubits: int = None
) -> None: ) -> None:
r"""Add phase damping channels. r"""Add phase damping channels.
...@@ -1229,7 +1292,8 @@ class Circuit(Sequential): ...@@ -1229,7 +1292,8 @@ class Circuit(Sequential):
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def depolarizing( def depolarizing(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full',
num_qubits: int = None
) -> None: ) -> None:
r"""Add depolarizing channels. r"""Add depolarizing channels.
...@@ -1243,7 +1307,8 @@ class Circuit(Sequential): ...@@ -1243,7 +1307,8 @@ class Circuit(Sequential):
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def pauli_channel( def pauli_channel(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full',
num_qubits: int = None
) -> None: ) -> None:
r"""Add Pauli channels. r"""Add Pauli channels.
...@@ -1257,7 +1322,8 @@ class Circuit(Sequential): ...@@ -1257,7 +1322,8 @@ class Circuit(Sequential):
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def reset_channel( def reset_channel(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full',
num_qubits: int = None
) -> None: ) -> None:
r"""Add reset channels. r"""Add reset channels.
...@@ -1271,8 +1337,8 @@ class Circuit(Sequential): ...@@ -1271,8 +1337,8 @@ class Circuit(Sequential):
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def thermal_relaxation( def thermal_relaxation(
self, const_t: Union[paddle.Tensor, Iterable[float]], exec_time: Union[paddle.Tensor, float], self, const_t: Union[paddle.Tensor, Iterable[float]], exec_time: Union[paddle.Tensor, float],
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
) -> None: ) -> None:
r"""Add thermal relaxation channels. r"""Add thermal relaxation channels.
...@@ -1286,6 +1352,20 @@ class Circuit(Sequential): ...@@ -1286,6 +1352,20 @@ class Circuit(Sequential):
self.append(ThermalRelaxation(const_t, exec_time, qubits_idx, self.append(ThermalRelaxation(const_t, exec_time, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def mixed_unitary_channel(
self, num_unitary: Union[paddle.Tensor, int], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
) -> None:
r"""Add mixed random unitary channels
Args:
num_unitary: The amount of random unitaries to be generated.
qubits_idx: Indices of the qubits on which the channels act. Defaults to ``'full'``.
num_qubits: Total number of qubits. Defaults to ``None``.
"""
self.__num_qubits_update(qubits_idx)
self.append(MixedUnitaryChannel(num_unitary, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits))
def kraus_repr( def kraus_repr(
self, kraus_oper: Iterable[paddle.Tensor], self, kraus_oper: Iterable[paddle.Tensor],
qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int], qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int],
...@@ -1321,7 +1401,7 @@ class Circuit(Sequential): ...@@ -1321,7 +1401,7 @@ class Circuit(Sequential):
num_qubits = self.__num_qubits num_qubits = self.__num_qubits
else: else:
assert num_qubits >= self.__num_qubits assert num_qubits >= self.__num_qubits
backend = get_backend() backend = get_backend()
self.to(backend=Backend.UnitaryMatrix) self.to(backend=Backend.UnitaryMatrix)
unitary = State(paddle.eye(2 ** num_qubits).cast(get_dtype()), unitary = State(paddle.eye(2 ** num_qubits).cast(get_dtype()),
...@@ -1337,11 +1417,11 @@ class Circuit(Sequential): ...@@ -1337,11 +1417,11 @@ class Circuit(Sequential):
Returns: Returns:
history of quantum gates of circuit history of quantum gates of circuit
""" """
gate_history = [] gate_history = []
for gate in self.sublayers(): for gate in self.sublayers():
if gate.gate_name is None: if gate.gate_info['gatename'] is None:
raise NotImplementedError(f"Gate {type(gate)} has no gate name and hence cannot be recorded into history.") raise NotImplementedError(f"Gate {type(gate)} has no gate name and hence cannot be recorded into history.")
else: else:
gate.gate_history_generation() gate.gate_history_generation()
...@@ -1369,7 +1449,6 @@ class Circuit(Sequential): ...@@ -1369,7 +1449,6 @@ class Circuit(Sequential):
if qubit[curr_qubit] > qubit_max: if qubit[curr_qubit] > qubit_max:
length.append(5) length.append(5)
qubit_max = qubit[curr_qubit] qubit_max = qubit[curr_qubit]
# Gates with params to print
elif current_gate['gate'] in {'p', 'rx', 'ry', 'rz'}: elif current_gate['gate'] in {'p', 'rx', 'ry', 'rz'}:
curr_qubit = current_gate['which_qubits'] curr_qubit = current_gate['which_qubits']
gate.append(qubit[curr_qubit]) gate.append(qubit[curr_qubit])
...@@ -1379,13 +1458,9 @@ class Circuit(Sequential): ...@@ -1379,13 +1458,9 @@ class Circuit(Sequential):
if qubit[curr_qubit] > qubit_max: if qubit[curr_qubit] > qubit_max:
length.append(5) length.append(5)
qubit_max = qubit[curr_qubit] qubit_max = qubit[curr_qubit]
# Two-qubit gates or Three-qubit gates elif current_gate['gate'] in {'cnot', 'swap', 'rxx', 'ryy', 'rzz', 'ms',
elif ( 'cy', 'cz', 'cu', 'cp', 'crx', 'cry', 'crz',
current_gate['gate'] in { 'cswap', 'ccx'}:
'cnot', 'swap', 'rxx', 'ryy', 'rzz', 'ms',
'cy', 'cz', 'cu', 'cp', 'crx', 'cry', 'crz'} or
current_gate['gate'] in {'cswap', 'ccx'}
):
a = max(current_gate['which_qubits']) a = max(current_gate['which_qubits'])
b = min(current_gate['which_qubits']) b = min(current_gate['which_qubits'])
ind = max(qubit[b: a + 1]) ind = max(qubit[b: a + 1])
...@@ -1400,11 +1475,11 @@ class Circuit(Sequential): ...@@ -1400,11 +1475,11 @@ class Circuit(Sequential):
qubit_max = ind + 1 qubit_max = ind + 1
return length, gate return length, gate
@property @property
def qubit_history(self) -> List[List[Tuple[Dict[str, Union[str, List[int], paddle.Tensor]], int]]]: def qubit_history(self) -> List[List[Tuple[Dict[str, Union[str, List[int], paddle.Tensor]], int]]]:
r""" gate information on each qubit r""" gate information on each qubit
Returns: Returns:
list of gate history on each qubit list of gate history on each qubit
...@@ -1412,10 +1487,7 @@ class Circuit(Sequential): ...@@ -1412,10 +1487,7 @@ class Circuit(Sequential):
The entry ``qubit_history[i][j][0/1]`` returns the gate information / gate index of the j-th gate The entry ``qubit_history[i][j][0/1]`` returns the gate information / gate index of the j-th gate
applied on the i-th qubit. applied on the i-th qubit.
""" """
history_qubit = [] history_qubit = [[] for _ in range(self.num_qubits)]
for i in range(self.num_qubits):
history_i = []
history_qubit.append(history_i)
for idx, i in enumerate(self.gate_history): for idx, i in enumerate(self.gate_history):
qubits = i["which_qubits"] qubits = i["which_qubits"]
if not isinstance(qubits, Iterable): if not isinstance(qubits, Iterable):
...@@ -1542,6 +1614,67 @@ class Circuit(Sequential): ...@@ -1542,6 +1614,67 @@ class Circuit(Sequential):
return return_str return return_str
def plot(self,
save_path: Optional[str] = None,
dpi: Optional[int] = 100,
show: Optional[bool] = True,
output: Optional[bool] = False,
scale: Optional[float] = 1.0,
tex: Optional[bool] = False,
) -> Union[None, matplotlib.figure.Figure]:
r'''display the circuit using matplotlib
Args:
save_path: the save path of image
dpi: dots per inches, here is resolution ratio
show: whether execute ``plt.show()``
output: whether return the ``matplotlib.figure.Figure`` instance
scale: scale coefficient of figure, default to 1.0
tex: a bool flag which controls latex fonts of gate display, default to ``False``.
Returns:
a ``matplotlib.figure.Figure`` instance or ``None`` depends on ``output``
Note:
Using ``plt.show()`` may cause a distortion, but it will not happen in the figure saved.
If the depth is too long, there will be some patches unable to display.
Setting ``tex = True`` requires that you have TeX and the other dependencies properly
installed on your system. See
https://matplotlib.org/stable/gallery/text_labels_and_annotations/tex_demo.html
for more details.
'''
_fig = _circuit_plot(self, dpi=dpi, scale=scale, tex = tex)
if save_path:
plt.savefig(save_path, dpi=dpi,)
if show: # whether display in window
plt.show()
if output:
return _fig # return the ``matplotlib.pyplot.figure`` instance
def extend(self, cir):
r""" extend for quantum circuit
Args:
cir: a Circuit or a Sequential
Returns:
concatenation of two quantum circuits
"""
if isinstance(cir, Circuit):
if self.__num_qubits is None:
self.__num_qubits = cir.num_qubits
else:
self.__num_qubits = self.__num_qubits if cir.num_qubits is None else max(self.__num_qubits, cir.num_qubits)
super().extend(cir)
elif isinstance(cir, Sequential):
super().extend(cir)
else:
raise TypeError("the input type must be Circuit or Sequential")
def forward(self, state: Optional[State] = None) -> State: def forward(self, state: Optional[State] = None) -> State:
r""" forward the input r""" forward the input
...@@ -1561,3 +1694,5 @@ class Circuit(Sequential): ...@@ -1561,3 +1694,5 @@ class Circuit(Sequential):
f"num_qubits does not agree: expected {self.__num_qubits}, received {state.num_qubits}" f"num_qubits does not agree: expected {self.__num_qubits}, received {state.num_qubits}"
return super().forward(state) return super().forward(state)
...@@ -26,6 +26,39 @@ from paddle_quantum.linalg import is_unitary ...@@ -26,6 +26,39 @@ from paddle_quantum.linalg import is_unitary
from typing import Callable, List, Any, Optional from typing import Callable, List, Any, Optional
def cir_decompose(cir: Circuit, trainable: Optional[bool] = False) -> Circuit:
r"""Decompose all layers of circuit into gates, and make all parameterized gates trainable if needed
Args:
cir: Target quantum circuit.
trainable: whether the decomposed parameterized gates are trainable
Returns:
A quantum circuit with same structure and parameters but all layers are decomposed into Gates.
Note:
This function does not support customized gates, such as oracle and control-oracle.
"""
gates_history = cir.gate_history
new_cir = Circuit()
for gate_info in gates_history:
gate_name = gate_info['gate']
qubits_idx = gate_info['which_qubits']
param = gate_info['theta']
# get gate function
if param is None:
getattr(new_cir, gate_name)(qubits_idx)
continue
if trainable:
param = param.reshape([1] + param.shape)
param = paddle.create_parameter(
shape=param.shape, dtype=param.dtype,
default_initializer=paddle.nn.initializer.Assign(param))
getattr(new_cir, gate_name)(qubits_idx, param=param)
return new_cir
class Inserter: class Inserter:
r"""Class for block insertion for the circuit. r"""Class for block insertion for the circuit.
""" """
...@@ -125,7 +158,8 @@ class Inserter: ...@@ -125,7 +158,8 @@ class Inserter:
cir.insert(insert_ind + 5, RZ([qubit_j], param=theta[4])) cir.insert(insert_ind + 5, RZ([qubit_j], param=theta[4]))
cir.insert(insert_ind + 6, RX([qubit_j], param=theta[5])) cir.insert(insert_ind + 6, RX([qubit_j], param=theta[5]))
cir.insert(insert_ind + 7, CNOT([qubit_i, qubit_j])) cir.insert(insert_ind + 7, CNOT([qubit_i, qubit_j]))
return cir
return cir_decompose(cir, trainable=True)
@classmethod @classmethod
def __count_qubit_gates(cls, cir: Circuit) -> np.ndarray: def __count_qubit_gates(cls, cir: Circuit) -> np.ndarray:
...@@ -144,14 +178,13 @@ class Inserter: ...@@ -144,14 +178,13 @@ class Inserter:
history = cir.gate_history history = cir.gate_history
for gate_info in history: for gate_info in history:
qubits_idx = gate_info["which_qubits"] qubits_idx = gate_info["which_qubits"]
if gate_info["gate"] == "rz" or gate_info["gate"] == "rx": if gate_info["gate"] in ["rz", "rx"]:
qubit_ind = qubits_idx qubit_ind = qubits_idx
count_gates[qubit_ind] += 1 count_gates[qubit_ind] += 1
elif gate_info["gate"] == "cnot": elif gate_info["gate"] == "cnot":
qubit_i = min(qubits_idx[0], qubits_idx[1]) qubit_i = min(qubits_idx[0], qubits_idx[1])
qubit_j = max(qubits_idx[0], qubits_idx[1]) qubit_j = max(qubits_idx[0], qubits_idx[1])
idx = (2 * cir.num_qubits - qubit_i) * \ idx = (2 * cir.num_qubits - qubit_i) * (qubit_i + 1) // 2 + qubit_j - qubit_i - 1
(qubit_i + 1) // 2 + qubit_j - qubit_i - 1
count_gates[idx] += 1 count_gates[idx] += 1
return count_gates return count_gates
...@@ -179,18 +212,11 @@ class Simplifier: ...@@ -179,18 +212,11 @@ class Simplifier:
cnot_qubits = history_i[0][0]["which_qubits"] cnot_qubits = history_i[0][0]["which_qubits"]
# find the other qubit # find the other qubit
for j in cnot_qubits: for j in cnot_qubits:
if j != i: if j != i and \
# check the CNOT is also in the front for the other qubit qubit_history[j][0][0]["gate"] == "cnot" and \
if ( qubit_history[j][0][0]["which_qubits"] == cnot_qubits:
qubit_history[j][0][0]["gate"] == "cnot" count += 1
and qubit_history[j][0][0]["which_qubits"] return count == 0
== cnot_qubits
):
count += 1
if count == 0:
return True
else:
return False
@classmethod @classmethod
def __check_consec_cnot(cls, cir: Circuit) -> bool: def __check_consec_cnot(cls, cir: Circuit) -> bool:
...@@ -216,16 +242,13 @@ class Simplifier: ...@@ -216,16 +242,13 @@ class Simplifier:
): ):
cnot_qubits = history_i[j][0]["which_qubits"] cnot_qubits = history_i[j][0]["which_qubits"]
# get the other qubit # get the other qubit
k = list(set(cnot_qubits).difference(set([i])))[0] k = list(set(cnot_qubits).difference({i}))[0]
# check if the found consecutive cnots are also consecutive on the other qubit # check if the found consecutive cnots are also consecutive on the other qubit
history_k = qubit_history[k] history_k = qubit_history[k]
idx_k = history_k.index(history_i[j]) idx_k = history_k.index(history_i[j])
if history_k[idx_k + 1] == history_i[j + 1]: if history_k[idx_k + 1] == history_i[j + 1]:
count += 1 count += 1
if count == 0: return count == 0
return True
else:
return False
@classmethod @classmethod
def __check_rz_init(cls, cir: Circuit) -> bool: def __check_rz_init(cls, cir: Circuit) -> bool:
...@@ -243,12 +266,9 @@ class Simplifier: ...@@ -243,12 +266,9 @@ class Simplifier:
history_i = qubit_history[i] history_i = qubit_history[i]
if not history_i: if not history_i:
continue continue
if history_i[0][0]["gate"] == "z" or history_i[0][0]["gate"] == "rz": if history_i[0][0]["gate"] in ["z", "rz"]:
count += 1 count += 1
if count == 0: return count == 0
return True
else:
return False
@classmethod @classmethod
def __check_repeated_rotations(cls, cir: Circuit) -> bool: def __check_repeated_rotations(cls, cir: Circuit) -> bool:
...@@ -275,10 +295,7 @@ class Simplifier: ...@@ -275,10 +295,7 @@ class Simplifier:
and history_i[j + 1][0]["gate"] == "rz" and history_i[j + 1][0]["gate"] == "rz"
): ):
count += 1 count += 1
if count == 0: return count == 0
return True
else:
return False
@classmethod @classmethod
def __check_4_consec_rotations(cls, cir: Circuit) -> bool: def __check_4_consec_rotations(cls, cir: Circuit) -> bool:
...@@ -309,10 +326,7 @@ class Simplifier: ...@@ -309,10 +326,7 @@ class Simplifier:
and history_i[j + 3][0]["gate"] == "rx" and history_i[j + 3][0]["gate"] == "rx"
): ):
count += 1 count += 1
if count == 0: return count == 0
return True
else:
return False
@classmethod @classmethod
def __check_rz_cnot_rz_rx_cnot_rx(cls, cir: Circuit) -> bool: def __check_rz_cnot_rz_rx_cnot_rx(cls, cir: Circuit) -> bool:
...@@ -344,10 +358,7 @@ class Simplifier: ...@@ -344,10 +358,7 @@ class Simplifier:
and history_i[j + 1][0]["which_qubits"][1] == i and history_i[j + 1][0]["which_qubits"][1] == i
): ):
count += 1 count += 1
if count == 0: return count == 0
return True
else:
return False
@classmethod @classmethod
def __rule_1(cls, cir: Circuit) -> Circuit: def __rule_1(cls, cir: Circuit) -> Circuit:
...@@ -369,15 +380,11 @@ class Simplifier: ...@@ -369,15 +380,11 @@ class Simplifier:
cnot_qubits = history_i[0][0]["which_qubits"] cnot_qubits = history_i[0][0]["which_qubits"]
# find the other qubit # find the other qubit
for j in cnot_qubits: for j in cnot_qubits:
if j != i: if j != i and \
# check the CNOT is also in the front for the other qubit qubit_history[j][0][0]["gate"] == "cnot" and \
if ( qubit_history[j][0][0]["which_qubits"] == cnot_qubits:
qubit_history[j][0][0]["gate"] == "cnot" # delete the gate
and qubit_history[j][0][0]["which_qubits"] cir.pop(qubit_history[j][0][1])
== cnot_qubits
):
# delete the gate
cir.pop(qubit_history[j][0][1])
qubit_history = cir.qubit_history qubit_history = cir.qubit_history
history_i = cir.qubit_history[i] history_i = cir.qubit_history[i]
return cir return cir
...@@ -409,7 +416,7 @@ class Simplifier: ...@@ -409,7 +416,7 @@ class Simplifier:
): ):
cnot_qubits = history_i[j][0]["which_qubits"] cnot_qubits = history_i[j][0]["which_qubits"]
# get the other qubit # get the other qubit
k = list(set(cnot_qubits).difference(set([i])))[0] k = list(set(cnot_qubits).difference({i}))[0]
# check if the found consecutive cnots are also consecutive on the other qubit # check if the found consecutive cnots are also consecutive on the other qubit
history_k = qubit_history[k] history_k = qubit_history[k]
idx_k = history_k.index(history_i[j]) idx_k = history_k.index(history_i[j])
...@@ -437,7 +444,7 @@ class Simplifier: ...@@ -437,7 +444,7 @@ class Simplifier:
history_i = cir.qubit_history[i] history_i = cir.qubit_history[i]
if not history_i: if not history_i:
continue continue
if history_i[0][0]["gate"] == "z" or history_i[0][0]["gate"] == "rz": if history_i[0][0]["gate"] in ["z", "rz"]:
# delete from history # delete from history
cir.pop(history_i[0][1]) cir.pop(history_i[0][1])
return cir return cir
...@@ -709,41 +716,8 @@ class Simplifier: ...@@ -709,41 +716,8 @@ class Simplifier:
cir = cls.__rule_5(cir) cir = cls.__rule_5(cir)
cir = cls.__rule_6(cir) cir = cls.__rule_6(cir)
return cir return cir_decompose(cir, trainable=True)
def cir_decompose(cir: Circuit, trainable: Optional[bool] = False) -> Circuit:
r"""Decompose all layers of circuit into gates, and make all parameterized gates trainable if needed
Args:
cir: Target quantum circuit.
trainable: whether the decomposed parameterized gates are trainable
Returns:
A quantum circuit with same structure and parameters but all layers are decomposed into Gates.
Note:
This function does not support customized gates, such as oracle and control-oracle.
"""
gates_history = cir.gate_history
new_cir = Circuit()
for gate_info in gates_history:
gate_name = gate_info['gate']
qubits_idx = gate_info['which_qubits']
param = gate_info['theta']
# get gate function
if param is None:
getattr(new_cir, gate_name)(qubits_idx)
continue
if trainable:
param = param.reshape([1] + param.shape)
param = paddle.create_parameter(
shape=param.shape, dtype=param.dtype,
default_initializer=paddle.nn.initializer.Assign(param))
getattr(new_cir, gate_name)(qubits_idx, param=param)
return new_cir
class VAns: class VAns:
r"""Class of Variable Ansatz. r"""Class of Variable Ansatz.
...@@ -835,14 +809,13 @@ class VAns: ...@@ -835,14 +809,13 @@ class VAns:
itr_loss = self.optimization(self.cir) itr_loss = self.optimization(self.cir)
self.loss = itr_loss self.loss = itr_loss
else: # insert + simplification else: # insert + simplification
# Insert new_cir = self.cir
new_cir = cir_decompose(self.cir, trainable=True)
new_cir = Inserter.insert_identities( new_cir = Inserter.insert_identities(
new_cir, self.insert_rate, self.epsilon) new_cir, self.insert_rate, self.epsilon)
new_cir = Simplifier.simplify_circuit( new_cir = Simplifier.simplify_circuit(
new_cir, self.zero_init_state) new_cir, self.zero_init_state)
itr_loss = self.optimization(new_cir) itr_loss = self.optimization(new_cir)
relative_diff = (itr_loss - self.loss) / abs(itr_loss) relative_diff = (itr_loss - self.loss) / abs(itr_loss)
...@@ -879,8 +852,6 @@ class VAns: ...@@ -879,8 +852,6 @@ class VAns:
Returns: Returns:
Optimized loss. Optimized loss.
""" """
cir = cir_decompose(cir, trainable=True)
opt = paddle.optimizer.Adam( opt = paddle.optimizer.Adam(
learning_rate=self.LR, parameters=cir.parameters()) learning_rate=self.LR, parameters=cir.parameters())
......
...@@ -28,5 +28,7 @@ from .common import Depolarizing ...@@ -28,5 +28,7 @@ from .common import Depolarizing
from .common import PauliChannel from .common import PauliChannel
from .common import ResetChannel from .common import ResetChannel
from .common import ThermalRelaxation from .common import ThermalRelaxation
from .common import MixedUnitaryChannel
from .custom import KrausRepr from .custom import KrausRepr
from .custom import ChoiRepr from .custom import ChoiRepr
from .custom import StinespringRepr
...@@ -19,7 +19,8 @@ The source file of the classes for several quantum channel. ...@@ -19,7 +19,8 @@ The source file of the classes for several quantum channel.
import paddle import paddle
import paddle_quantum import paddle_quantum
from paddle_quantum.intrinsic import _format_qubits_idx from ..intrinsic import _format_qubits_idx
from ..qinfo import kraus_oper_random
from .base import Channel from .base import Channel
from . import functional from . import functional
from ..backend import Backend from ..backend import Backend
...@@ -47,10 +48,7 @@ class BitFlip(Channel): ...@@ -47,10 +48,7 @@ class BitFlip(Channel):
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, float): self.prob = paddle.to_tensor(prob) if isinstance(prob, (int, float)) else prob
self.prob = paddle.to_tensor(prob)
else:
self.prob = prob
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -81,10 +79,7 @@ class PhaseFlip(Channel): ...@@ -81,10 +79,7 @@ class PhaseFlip(Channel):
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, float): self.prob = paddle.to_tensor(prob) if isinstance(prob, (int, float)) else prob
self.prob = paddle.to_tensor(prob)
else:
self.prob = prob
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -115,10 +110,7 @@ class BitPhaseFlip(Channel): ...@@ -115,10 +110,7 @@ class BitPhaseFlip(Channel):
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, float): self.prob = paddle.to_tensor(prob) if isinstance(prob, (int, float)) else prob
self.prob = paddle.to_tensor(prob)
else:
self.prob = prob
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -157,10 +149,7 @@ class AmplitudeDamping(Channel): ...@@ -157,10 +149,7 @@ class AmplitudeDamping(Channel):
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(gamma, float): self.gamma = paddle.to_tensor(gamma) if isinstance(gamma, (int, float)) else gamma
self.gamma = paddle.to_tensor(gamma)
else:
self.gamma = gamma
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -198,14 +187,8 @@ class GeneralizedAmplitudeDamping(Channel): ...@@ -198,14 +187,8 @@ class GeneralizedAmplitudeDamping(Channel):
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, float): self.prob = paddle.to_tensor(prob) if isinstance(prob, (int, float)) else prob
self.prob = paddle.to_tensor(prob) self.gamma = paddle.to_tensor(gamma) if isinstance(gamma, (int, float)) else gamma
else:
self.prob = prob
if isinstance(gamma, float):
self.gamma = paddle.to_tensor(gamma)
else:
self.gamma = gamma
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -245,10 +228,7 @@ class PhaseDamping(Channel): ...@@ -245,10 +228,7 @@ class PhaseDamping(Channel):
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(gamma, float): self.gamma = paddle.to_tensor(gamma) if isinstance(gamma, (int, float)) else gamma
self.gamma = paddle.to_tensor(gamma)
else:
self.gamma = gamma
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -265,15 +245,22 @@ class Depolarizing(Channel): ...@@ -265,15 +245,22 @@ class Depolarizing(Channel):
.. math:: .. math::
E_0 = \sqrt{1-p} I, E_0 = \sqrt{1-3p/4} I,
E_1 = \sqrt{p/3} X, E_1 = \sqrt{p/4} X,
E_2 = \sqrt{p/3} Y, E_2 = \sqrt{p/4} Y,
E_3 = \sqrt{p/3} Z. E_3 = \sqrt{p/4} Z.
Args: Args:
prob: Parameter of the depolarizing channels. Its value should be in the range :math:`[0, 1]`. prob: Parameter of the depolarizing channels. Its value should be in the range :math:`[0, 1]`.
qubits_idx: Indices of the qubits on which the channels act. Defaults to ``'full'``. qubits_idx: Indices of the qubits on which the channels act. Defaults to ``'full'``.
num_qubits: Total number of qubits. Defaults to ``None``. num_qubits: Total number of qubits. Defaults to ``None``.
Note:
The implementation logic for this feature has been updated.
The current version refers to formula (8.102) in Quantum Computation and Quantum Information 10th
edition by M.A.Nielsen and I.L.Chuang.
Reference: Nielsen, M., & Chuang, I. (2010). Quantum Computation and Quantum Information: 10th
Anniversary Edition. Cambridge: Cambridge University Press. doi:10.1017/CBO9780511976667
""" """
def __init__( def __init__(
self, prob: Union[paddle.Tensor, float], self, prob: Union[paddle.Tensor, float],
...@@ -281,10 +268,7 @@ class Depolarizing(Channel): ...@@ -281,10 +268,7 @@ class Depolarizing(Channel):
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, float): self.prob = paddle.to_tensor(prob) if isinstance(prob, (int, float)) else prob
self.prob = paddle.to_tensor(prob)
else:
self.prob = prob
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -312,10 +296,7 @@ class PauliChannel(Channel): ...@@ -312,10 +296,7 @@ class PauliChannel(Channel):
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, Iterable): self.prob = paddle.to_tensor(prob) if isinstance(prob, Iterable) else prob
self.prob = paddle.to_tensor(prob)
else:
self.prob = prob
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -370,10 +351,7 @@ class ResetChannel(Channel): ...@@ -370,10 +351,7 @@ class ResetChannel(Channel):
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, Iterable): self.prob = paddle.to_tensor(prob) if isinstance(prob, Iterable) else prob
self.prob = paddle.to_tensor(prob)
else:
self.prob = prob
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -403,14 +381,8 @@ class ThermalRelaxation(Channel): ...@@ -403,14 +381,8 @@ class ThermalRelaxation(Channel):
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(const_t, float): self.const_t = paddle.to_tensor(const_t) if isinstance(const_t, (int, float)) else const_t
self.const_t = paddle.to_tensor(const_t) self.exec_time = paddle.to_tensor(exec_time) if isinstance(exec_time, (int, float)) else exec_time
else:
self.const_t = const_t
if isinstance(exec_time, float):
self.exec_time = paddle.to_tensor(exec_time)
else:
self.exec_time = exec_time
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -419,3 +391,35 @@ class ThermalRelaxation(Channel): ...@@ -419,3 +391,35 @@ class ThermalRelaxation(Channel):
state = functional.thermal_relaxation( state = functional.thermal_relaxation(
state, self.const_t, self.exec_time, qubit_idx, self.dtype, self.backend) state, self.const_t, self.exec_time, qubit_idx, self.dtype, self.backend)
return state return state
class MixedUnitaryChannel(Channel):
r"""A collection of mixed unitary channels.
Such a channel's Kraus operators are randomly generated unitaries times related probabilities
.. math::
N(\rho) = \sum_{i} p_{i} U_{i} \rho U_{i}^{\dagger}
Args:
num_unitary: The amount of random unitaries to be generated.
qubits_idx: Indices of the qubits on which the channels act. Defaults to ``'full'``.
num_qubits: Total number of qubits. Defaults to ``None``.
Note:
The probability distribution of unitaries is set to be uniform distribution.
"""
def __init__(
self, num_unitary: int,
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
):
super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.kraus_oper = kraus_oper_random(1, num_unitary)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
raise NotImplementedError
for qubit_idx in self.qubits_idx:
state = functional.kraus_repr(state, self.kraus_oper, qubit_idx, self.dtype, self.backend)
return state
...@@ -19,11 +19,13 @@ The source file of the classes for custom quantum channels. ...@@ -19,11 +19,13 @@ The source file of the classes for custom quantum channels.
import math import math
import paddle import paddle
import warnings
from typing import Union, Iterable
import paddle_quantum import paddle_quantum
from .base import Channel from .base import Channel
from paddle_quantum.intrinsic import _format_qubits_idx
from . import functional from . import functional
from typing import Union, Iterable from ..intrinsic import _format_qubits_idx
class KrausRepr(Channel): class KrausRepr(Channel):
...@@ -35,23 +37,94 @@ class KrausRepr(Channel): ...@@ -35,23 +37,94 @@ class KrausRepr(Channel):
num_qubits: Total number of qubits. Defaults to ``None``. num_qubits: Total number of qubits. Defaults to ``None``.
""" """
def __init__( def __init__(
self, kraus_oper: Iterable[paddle.Tensor], self, kraus_oper: Union[paddle.Tensor, Iterable[paddle.Tensor]],
qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int], qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int],
num_qubits: int = None num_qubits: int = None
): ):
# TODO: need to check whether the input is legal
super().__init__() super().__init__()
num_acted_qubits = int(math.log2(kraus_oper[0].shape[0])) num_acted_qubits = int(math.log2(kraus_oper[0].shape[0]))
assert 2 ** num_acted_qubits == kraus_oper[0].shape[0], "The length of oracle should be integer power of 2." assert 2 ** num_acted_qubits == kraus_oper[0].shape[0], "The length of oracle should be integer power of 2."
self.kraus_oper = kraus_oper
is_single_qubit = True if num_acted_qubits == 1 else False self.kraus_oper = [oper.cast(self.dtype) for oper in kraus_oper] if isinstance(kraus_oper, Iterable) else [kraus_oper.cast(self.dtype)]
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits)
# sanity check
dimension = 2 ** num_acted_qubits
oper_sum = paddle.zeros([dimension, dimension]).cast(self.dtype)
for oper in self.kraus_oper:
oper_sum = oper_sum + oper @ paddle.conj(oper.T)
err = paddle.norm(paddle.abs(oper_sum - paddle.eye(dimension).cast(self.dtype))).item()
if err > min(1e-6 * dimension * len(kraus_oper), 0.01):
warnings.warn(
f"\nThe input data may not be a Kraus representation of a channel: norm(sum(E * E^d) - I) = {err}.", UserWarning)
def forward(self, state: 'paddle_quantum.State') -> 'paddle_quantum.State': def __matmul__(self, other: 'KrausRepr') -> 'KrausRepr':
r"""Composition between channels with Kraus representations
"""
assert self.qubits_idx == other.qubits_idx, \
f"Two channels should have the same qubit indices to composite: received {self.qubits_idx} and {other.qubits_idx}"
if not isinstance(other, KrausRepr):
raise NotImplementedError(
f"does not support the composition between KrausRepr and {type(other)}")
new_kraus_oper = []
for this_kraus in self.kraus_oper:
new_kraus_oper.extend([this_kraus @ other_kraus for other_kraus in other.kraus_oper])
return KrausRepr(new_kraus_oper, self.qubits_idx)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
state = functional.kraus_repr(state, self.kraus_oper, qubits_idx, self.dtype, self.backend) state = functional.kraus_repr(state, self.kraus_oper, qubits_idx, self.dtype, self.backend)
return state return state
class ChoiRepr(Channel): class ChoiRepr(Channel):
pass def __init__(
self,
choi_oper: paddle.Tensor,
qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int],
num_qubits: int = None
):
super().__init__()
num_acted_qubits = int(math.log2(choi_oper.shape[0]) / 2)
assert 2 ** (2 * num_acted_qubits) == choi_oper.shape[0], "The length of oracle should be integer power of 2."
self.choi_oper = choi_oper
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for qubits_idx in self.qubits_idx:
state = functional.choi_repr(
state,
self.choi_oper,
qubits_idx,
self.dtype,
self.backend
)
return state
class StinespringRepr(Channel):
def __init__(
self,
stinespring_matrix: paddle.Tensor,
qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int],
num_qubits: int = None
):
super().__init__()
num_acted_qubits = int(math.log2(stinespring_matrix.shape[1]))
dim_ancilla = stinespring_matrix.shape[0] // stinespring_matrix.shape[1]
dim_act = stinespring_matrix.shape[1]
assert dim_act * dim_ancilla == stinespring_matrix.shape[0], 'The width of stinespring matrix should be the factor of its height'
self.stinespring_matrix = stinespring_matrix
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for qubits_idx in self.qubits_idx:
state = functional.stinespring_repr(
state,
self.stinespring_matrix,
qubits_idx,
self.dtype,
self.backend
)
return state
...@@ -29,3 +29,4 @@ from .common import reset_channel ...@@ -29,3 +29,4 @@ from .common import reset_channel
from .common import thermal_relaxation from .common import thermal_relaxation
from .common import kraus_repr from .common import kraus_repr
from .common import choi_repr from .common import choi_repr
from .common import stinespring_repr
...@@ -22,11 +22,12 @@ import paddle ...@@ -22,11 +22,12 @@ import paddle
import paddle_quantum import paddle_quantum
from ...backend import density_matrix from ...backend import density_matrix
from ...intrinsic import _zero, _one from ...intrinsic import _zero, _one
from typing import Iterable from typing import Iterable, List, Tuple, Union
def bit_flip( def bit_flip(
state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: Union[List[int], int],
dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State: ) -> paddle_quantum.State:
r"""Apply a bit flip channel on the input state. r"""Apply a bit flip channel on the input state.
...@@ -58,7 +59,12 @@ def bit_flip( ...@@ -58,7 +59,12 @@ def bit_flip(
for idx, oper in enumerate(kraus_oper): for idx, oper in enumerate(kraus_oper):
kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2]) kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2])
state_data = [ state_data = [
density_matrix.unitary_transformation(state.data, oper, [qubit_idx], state.num_qubits) for oper in kraus_oper density_matrix.unitary_transformation(
state.data,
oper,
qubit_idx if isinstance(qubit_idx, list) else [qubit_idx],
state.num_qubits
) for oper in kraus_oper
] ]
state_data = functools.reduce(lambda x, y: x + y, state_data) state_data = functools.reduce(lambda x, y: x + y, state_data)
transformed_state = state.clone() transformed_state = state.clone()
...@@ -67,7 +73,8 @@ def bit_flip( ...@@ -67,7 +73,8 @@ def bit_flip(
def phase_flip( def phase_flip(
state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: Union[List[int], int],
dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State: ) -> paddle_quantum.State:
r"""Apply a phase flip channel on the input state. r"""Apply a phase flip channel on the input state.
...@@ -99,7 +106,12 @@ def phase_flip( ...@@ -99,7 +106,12 @@ def phase_flip(
for idx, oper in enumerate(kraus_oper): for idx, oper in enumerate(kraus_oper):
kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2]) kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2])
state_data = [ state_data = [
density_matrix.unitary_transformation(state.data, oper, [qubit_idx], state.num_qubits) for oper in kraus_oper density_matrix.unitary_transformation(
state.data,
oper,
qubit_idx if isinstance(qubit_idx, list) else [qubit_idx],
state.num_qubits
) for oper in kraus_oper
] ]
state_data = functools.reduce(lambda x, y: x + y, state_data) state_data = functools.reduce(lambda x, y: x + y, state_data)
transformed_state = state.clone() transformed_state = state.clone()
...@@ -108,7 +120,8 @@ def phase_flip( ...@@ -108,7 +120,8 @@ def phase_flip(
def bit_phase_flip( def bit_phase_flip(
state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: Union[List[int], int],
dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State: ) -> paddle_quantum.State:
r"""Apply a bit phase flip channel on the input state. r"""Apply a bit phase flip channel on the input state.
...@@ -140,7 +153,12 @@ def bit_phase_flip( ...@@ -140,7 +153,12 @@ def bit_phase_flip(
for idx, oper in enumerate(kraus_oper): for idx, oper in enumerate(kraus_oper):
kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2]) kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2])
state_data = [ state_data = [
density_matrix.unitary_transformation(state.data, oper, [qubit_idx], state.num_qubits) for oper in kraus_oper density_matrix.unitary_transformation(
state.data,
oper,
qubit_idx if isinstance(qubit_idx, list) else [qubit_idx],
state.num_qubits
) for oper in kraus_oper
] ]
state_data = functools.reduce(lambda x, y: x + y, state_data) state_data = functools.reduce(lambda x, y: x + y, state_data)
transformed_state = state.clone() transformed_state = state.clone()
...@@ -149,7 +167,8 @@ def bit_phase_flip( ...@@ -149,7 +167,8 @@ def bit_phase_flip(
def amplitude_damping( def amplitude_damping(
state: paddle_quantum.State, gamma: paddle.Tensor, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend state: paddle_quantum.State, gamma: paddle.Tensor, qubit_idx: Union[List[int], int],
dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State: ) -> paddle_quantum.State:
r"""Apply an amplitude damping channel on the input state. r"""Apply an amplitude damping channel on the input state.
...@@ -181,7 +200,12 @@ def amplitude_damping( ...@@ -181,7 +200,12 @@ def amplitude_damping(
for idx, oper in enumerate(kraus_oper): for idx, oper in enumerate(kraus_oper):
kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2]) kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2])
state_data = [ state_data = [
density_matrix.unitary_transformation(state.data, oper, [qubit_idx], state.num_qubits) for oper in kraus_oper density_matrix.unitary_transformation(
state.data,
oper,
qubit_idx if isinstance(qubit_idx, list) else [qubit_idx],
state.num_qubits
) for oper in kraus_oper
] ]
state_data = functools.reduce(lambda x, y: x + y, state_data) state_data = functools.reduce(lambda x, y: x + y, state_data)
transformed_state = state.clone() transformed_state = state.clone()
...@@ -191,7 +215,7 @@ def amplitude_damping( ...@@ -191,7 +215,7 @@ def amplitude_damping(
def generalized_amplitude_damping( def generalized_amplitude_damping(
state: paddle_quantum.State, gamma: paddle.Tensor, prob: paddle.Tensor, state: paddle_quantum.State, gamma: paddle.Tensor, prob: paddle.Tensor,
qubit_idx: int, dtype: str, backend: paddle_quantum.Backend qubit_idx: Union[List[int], int], dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State: ) -> paddle_quantum.State:
r"""Apply a generalized amplitude damping channel on the input state. r"""Apply a generalized amplitude damping channel on the input state.
...@@ -232,7 +256,12 @@ def generalized_amplitude_damping( ...@@ -232,7 +256,12 @@ def generalized_amplitude_damping(
for idx, oper in enumerate(kraus_oper): for idx, oper in enumerate(kraus_oper):
kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2]) kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2])
state_data = [ state_data = [
density_matrix.unitary_transformation(state.data, oper, [qubit_idx], state.num_qubits) for oper in kraus_oper density_matrix.unitary_transformation(
state.data,
oper,
qubit_idx if isinstance(qubit_idx, list) else [qubit_idx],
state.num_qubits
) for oper in kraus_oper
] ]
state_data = functools.reduce(lambda x, y: x + y, state_data) state_data = functools.reduce(lambda x, y: x + y, state_data)
transformed_state = state.clone() transformed_state = state.clone()
...@@ -241,7 +270,8 @@ def generalized_amplitude_damping( ...@@ -241,7 +270,8 @@ def generalized_amplitude_damping(
def phase_damping( def phase_damping(
state: paddle_quantum.State, gamma: paddle.Tensor, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend state: paddle_quantum.State, gamma: paddle.Tensor, qubit_idx: Union[List[int], int],
dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State: ) -> paddle_quantum.State:
r"""Apply a phase damping channel on the input state. r"""Apply a phase damping channel on the input state.
...@@ -273,7 +303,12 @@ def phase_damping( ...@@ -273,7 +303,12 @@ def phase_damping(
for idx, oper in enumerate(kraus_oper): for idx, oper in enumerate(kraus_oper):
kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2]) kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2])
state_data = [ state_data = [
density_matrix.unitary_transformation(state.data, oper, [qubit_idx], state.num_qubits) for oper in kraus_oper density_matrix.unitary_transformation(
state.data,
oper,
qubit_idx if isinstance(qubit_idx, list) else [qubit_idx],
state.num_qubits
) for oper in kraus_oper
] ]
state_data = functools.reduce(lambda x, y: x + y, state_data) state_data = functools.reduce(lambda x, y: x + y, state_data)
transformed_state = state.clone() transformed_state = state.clone()
...@@ -282,7 +317,8 @@ def phase_damping( ...@@ -282,7 +317,8 @@ def phase_damping(
def depolarizing( def depolarizing(
state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: Union[List[int], int],
dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State: ) -> paddle_quantum.State:
r"""Apply a depolarizing channel on the input state. r"""Apply a depolarizing channel on the input state.
...@@ -303,26 +339,31 @@ def depolarizing( ...@@ -303,26 +339,31 @@ def depolarizing(
raise RuntimeError("The noisy channel can only run in density matrix mode.") raise RuntimeError("The noisy channel can only run in density matrix mode.")
kraus_oper = [ kraus_oper = [
[ [
paddle.sqrt(1 - prob).cast(dtype), _zero(dtype), paddle.sqrt(1 - 3 * prob / 4).cast(dtype), _zero(dtype),
_zero(dtype), paddle.sqrt(1 - prob).cast(dtype), _zero(dtype), paddle.sqrt(1 - 3 * prob / 4).cast(dtype),
], ],
[ [
_zero(dtype), paddle.sqrt(prob / 3).cast(dtype), _zero(dtype), paddle.sqrt(prob / 4).cast(dtype),
paddle.sqrt(prob / 3).cast(dtype), _zero(dtype), paddle.sqrt(prob / 4).cast(dtype), _zero(dtype),
], ],
[ [
_zero(dtype), -1j * paddle.sqrt(prob / 3).cast(dtype), _zero(dtype), -1j * paddle.sqrt(prob / 4).cast(dtype),
1j * paddle.sqrt(prob / 3).cast(dtype), _zero(dtype), 1j * paddle.sqrt(prob / 4).cast(dtype), _zero(dtype),
], ],
[ [
paddle.sqrt(prob / 3).cast(dtype), _zero(dtype), paddle.sqrt(prob / 4).cast(dtype), _zero(dtype),
_zero(dtype), (-1 * paddle.sqrt(prob / 3)).cast(dtype), _zero(dtype), (-1 * paddle.sqrt(prob / 4)).cast(dtype),
], ],
] ]
for idx, oper in enumerate(kraus_oper): for idx, oper in enumerate(kraus_oper):
kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2]) kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2])
state_data = [ state_data = [
density_matrix.unitary_transformation(state.data, oper, [qubit_idx], state.num_qubits) for oper in kraus_oper density_matrix.unitary_transformation(
state.data,
oper,
qubit_idx if isinstance(qubit_idx, list) else [qubit_idx],
state.num_qubits
) for oper in kraus_oper
] ]
state_data = functools.reduce(lambda x, y: x + y, state_data) state_data = functools.reduce(lambda x, y: x + y, state_data)
transformed_state = state.clone() transformed_state = state.clone()
...@@ -331,7 +372,7 @@ def depolarizing( ...@@ -331,7 +372,7 @@ def depolarizing(
def pauli_channel( def pauli_channel(
state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: int, state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: Union[List[int], int],
dtype: str, backend: paddle_quantum.Backend dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State: ) -> paddle_quantum.State:
r"""Apply a Pauli channel on the input state. r"""Apply a Pauli channel on the input state.
...@@ -374,7 +415,12 @@ def pauli_channel( ...@@ -374,7 +415,12 @@ def pauli_channel(
for idx, oper in enumerate(kraus_oper): for idx, oper in enumerate(kraus_oper):
kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2]) kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2])
state_data = [ state_data = [
density_matrix.unitary_transformation(state.data, oper, [qubit_idx], state.num_qubits) for oper in kraus_oper density_matrix.unitary_transformation(
state.data,
oper,
qubit_idx if isinstance(qubit_idx, list) else [qubit_idx],
state.num_qubits
) for oper in kraus_oper
] ]
state_data = functools.reduce(lambda x, y: x + y, state_data) state_data = functools.reduce(lambda x, y: x + y, state_data)
transformed_state = state.clone() transformed_state = state.clone()
...@@ -383,7 +429,7 @@ def pauli_channel( ...@@ -383,7 +429,7 @@ def pauli_channel(
def reset_channel( def reset_channel(
state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: int, state: paddle_quantum.State, prob: paddle.Tensor, qubit_idx: Union[List[int], int],
dtype: str, backend: paddle_quantum.Backend dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State: ) -> paddle_quantum.State:
r"""Apply a reset channel on the input state. r"""Apply a reset channel on the input state.
...@@ -430,7 +476,12 @@ def reset_channel( ...@@ -430,7 +476,12 @@ def reset_channel(
for idx, oper in enumerate(kraus_oper): for idx, oper in enumerate(kraus_oper):
kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2]) kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2])
state_data = [ state_data = [
density_matrix.unitary_transformation(state.data, oper, [qubit_idx], state.num_qubits) for oper in kraus_oper density_matrix.unitary_transformation(
state.data,
oper,
qubit_idx if isinstance(qubit_idx, list) else [qubit_idx],
state.num_qubits
) for oper in kraus_oper
] ]
state_data = functools.reduce(lambda x, y: x + y, state_data) state_data = functools.reduce(lambda x, y: x + y, state_data)
transformed_state = state.clone() transformed_state = state.clone()
...@@ -440,7 +491,7 @@ def reset_channel( ...@@ -440,7 +491,7 @@ def reset_channel(
def thermal_relaxation( def thermal_relaxation(
state: paddle_quantum.State, const_t: paddle.Tensor, exec_time: paddle.Tensor, state: paddle_quantum.State, const_t: paddle.Tensor, exec_time: paddle.Tensor,
qubit_idx: int, dtype: str, backend: paddle_quantum.Backend qubit_idx: Union[List[int], int], dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State: ) -> paddle_quantum.State:
r"""Apply a thermal relaxation channel on the input state. r"""Apply a thermal relaxation channel on the input state.
...@@ -486,7 +537,12 @@ def thermal_relaxation( ...@@ -486,7 +537,12 @@ def thermal_relaxation(
for idx, oper in enumerate(kraus_oper): for idx, oper in enumerate(kraus_oper):
kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2]) kraus_oper[idx] = paddle.reshape(paddle.concat(oper), [2, 2])
state_data = [ state_data = [
density_matrix.unitary_transformation(state.data, oper, [qubit_idx], state.num_qubits) for oper in kraus_oper density_matrix.unitary_transformation(
state.data,
oper,
qubit_idx if isinstance(qubit_idx, list) else [qubit_idx],
state.num_qubits
) for oper in kraus_oper
] ]
state_data = functools.reduce(lambda x, y: x + y, state_data) state_data = functools.reduce(lambda x, y: x + y, state_data)
transformed_state = state.clone() transformed_state = state.clone()
...@@ -495,7 +551,7 @@ def thermal_relaxation( ...@@ -495,7 +551,7 @@ def thermal_relaxation(
def kraus_repr( def kraus_repr(
state: paddle_quantum.State, kraus_oper: Iterable[paddle.Tensor], qubit_idx: int, state: paddle_quantum.State, kraus_oper: Iterable[paddle.Tensor], qubit_idx: Union[List[int], int],
dtype: str, backend: paddle_quantum.Backend dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State: ) -> paddle_quantum.State:
r"""Apply a custom channel in the Kraus representation on the input state. r"""Apply a custom channel in the Kraus representation on the input state.
...@@ -515,9 +571,13 @@ def kraus_repr( ...@@ -515,9 +571,13 @@ def kraus_repr(
""" """
if backend != paddle_quantum.Backend.DensityMatrix: if backend != paddle_quantum.Backend.DensityMatrix:
raise RuntimeError("The noisy channel can only run in density matrix mode.") raise RuntimeError("The noisy channel can only run in density matrix mode.")
kraus_oper = [paddle.cast(oper, dtype) for oper in kraus_oper]
state_data = [ state_data = [
density_matrix.unitary_transformation(state.data, oper, [qubit_idx], state.num_qubits) for oper in kraus_oper density_matrix.unitary_transformation(
state.data,
oper,
qubit_idx if isinstance(qubit_idx, list) else [qubit_idx],
state.num_qubits
) for oper in kraus_oper
] ]
state_data = functools.reduce(lambda x, y: x + y, state_data) state_data = functools.reduce(lambda x, y: x + y, state_data)
transformed_state = state.clone() transformed_state = state.clone()
...@@ -525,5 +585,323 @@ def kraus_repr( ...@@ -525,5 +585,323 @@ def kraus_repr(
return transformed_state return transformed_state
def choi_repr(): def choi_repr(
raise NotImplementedError state: paddle_quantum.State, choi_oper: paddle.Tensor, qubit_idx: Union[List[int], int],
dtype: str, backend: paddle_quantum.Backend
) -> paddle_quantum.State:
r"""choi_repr implement
Assume the choi state has the shape of sum :math:`|i\rangle\langle j|` :math:`N(|i\rangle\langle j|)` .
Args:
state: input quantum state
choi_oper: choi representation for the channel
qubit_idx: which qubits the channel acts on
dtype: data dtype
backend: data backend
Raises:
RuntimeError: _description_
Returns:
paddle_quantum.State: output from the channel
"""
qubit_idx = qubit_idx if isinstance(qubit_idx, list) else [qubit_idx]
def genSwapList(origin: List[int], target: List[int]) -> List[Tuple[int, int]]:
assert len(origin) == len(target)
swapped = [False] * len(origin)
swap_ops = []
origin_pos_dict = {v: pos for pos, v in enumerate(origin)}
def positionOfValueAt(idx):
# return the position of value `target[idx]` in origin array
return origin_pos_dict[target[idx]]
ref = origin.copy()
for idx in range(len(origin)):
if not swapped[idx]:
next_idx = idx
swapped[next_idx] = True
while not swapped[positionOfValueAt(next_idx)]:
swapped[positionOfValueAt(next_idx)] = True
if next_idx < positionOfValueAt(next_idx):
swap_ops.append((next_idx, positionOfValueAt(next_idx)))
else:
swap_ops.append((positionOfValueAt(next_idx), next_idx))
x, y = swap_ops[-1]
ref[x], ref[y] = ref[y], ref[x]
# print(idx, (x,y), ref)
next_idx = positionOfValueAt(next_idx)
return swap_ops
if backend != paddle_quantum.Backend.DensityMatrix:
raise RuntimeError("The noisy channel can only run in density matrix mode.")
assert len(choi_oper) == 2 ** (2 * len(qubit_idx))
num_qubits = state.num_qubits
num_acted_qubits = len(qubit_idx)
# make partial transpose on the ancilla of choi repr, this leads to choi_mat as `sum |j><i| N(|i><j|)`
choi_mat = paddle.reshape(choi_oper, [2 ** num_acted_qubits, 2 ** num_acted_qubits,
2 ** num_acted_qubits, 2 ** num_acted_qubits])
choi_mat = paddle.transpose(choi_mat, [2, 1, 0, 3])
choi_mat = paddle.reshape(choi_mat, [2 ** (2 * num_acted_qubits), 2 ** (2 * num_acted_qubits)])
ext_state = paddle.kron(state.data, paddle.eye(2 ** num_acted_qubits))
ext_qubit_idx = qubit_idx + [num_qubits + x for x in range(num_acted_qubits)]
ext_num_qubits = num_qubits + num_acted_qubits
higher_dims = ext_state.shape[:-2]
num_higher_dims = len(higher_dims)
swap_ops = genSwapList(list(range(ext_num_qubits)), ext_qubit_idx +
[x for x in range(ext_num_qubits) if x not in ext_qubit_idx])
# make swap for left
for swap_op in swap_ops:
shape = higher_dims.copy()
last_idx = -1
for idx in swap_op:
shape.append(2 ** (idx - last_idx - 1))
shape.append(2)
last_idx = idx
shape.append(2 ** (2 * ext_num_qubits - last_idx - 1))
ext_state = paddle.reshape(ext_state, shape)
ext_state = paddle.transpose(
ext_state, list(range(num_higher_dims)) + [item + num_higher_dims for item in [0, 3, 2, 1, 4]]
)
# multiply the choi_matrix
ext_state = paddle.reshape(
ext_state, higher_dims.copy() + [2 ** (2 * num_acted_qubits), 2 ** (2 * ext_num_qubits - 2 * num_acted_qubits)]
)
ext_state = paddle.reshape(
paddle.matmul(choi_mat, ext_state), higher_dims.copy() + [2 ** ext_num_qubits, 2 ** ext_num_qubits]
)
# make swap for right
for swap_op in swap_ops:
shape = higher_dims.copy()
last_idx = -1
shape.append(2 ** ext_num_qubits)
for idx in swap_op:
shape.append(2 ** (idx - last_idx - 1))
shape.append(2)
last_idx = idx
shape.append(2 ** (ext_num_qubits - last_idx - 1))
ext_state = paddle.reshape(ext_state, shape)
ext_state = paddle.transpose(
ext_state, list(range(num_higher_dims)) + [item + num_higher_dims for item in [0, 1, 4, 3, 2, 5]]
)
# implement partial trace on ext_state
new_state = paddle.trace(
paddle.reshape(
ext_state,
higher_dims.copy() + [2 ** num_acted_qubits, 2 ** num_qubits, 2 ** num_acted_qubits, 2 ** num_qubits]
),
axis1=len(higher_dims),
axis2=2+len(higher_dims)
)
# swap back
revert_swap_ops = genSwapList(qubit_idx + [x for x in range(num_qubits) if x not in qubit_idx],
list(range(num_qubits)))
for swap_op in revert_swap_ops:
shape = higher_dims.copy()
last_idx = -1
for idx in swap_op:
shape.append(2 ** (idx - last_idx - 1))
shape.append(2)
last_idx = idx
shape.append(2 ** (2 * num_qubits - last_idx - 1))
new_state = paddle.reshape(new_state, shape)
new_state = paddle.transpose(
new_state, list(range(num_higher_dims)) + [item + num_higher_dims for item in [0, 3, 2, 1, 4]]
)
for swap_op in revert_swap_ops:
shape = higher_dims.copy()
last_idx = -1
shape.append(2 ** num_qubits)
for idx in swap_op:
shape.append(2 ** (idx - last_idx - 1))
shape.append(2)
last_idx = idx
shape.append(2 ** (num_qubits - last_idx - 1))
new_state = paddle.reshape(new_state, shape)
new_state = paddle.transpose(
new_state, list(range(num_higher_dims)) + [item + num_higher_dims for item in [0, 1, 4, 3, 2, 5]]
)
new_state = paddle.reshape(new_state, higher_dims.copy() + [2 ** num_qubits, 2 ** num_qubits])
return paddle_quantum.State(new_state, dtype=dtype, backend=backend)
def stinespring_repr(
state: paddle_quantum.State,
stinespring_mat: paddle.Tensor,
qubit_idx: Union[List[int], int],
dtype: str,
backend: paddle_quantum.Backend
):
"""stinespring representation for quantum channel
assuming stinespring_mat being the rectangle matrix of shape (dim1 * dim2, dim1)
where dim1 is the dimension of qubit_idx, dim2 needs to be partial traced. With
Dirac notation we have the elements
stinespring_mat.reshape([dim1, dim2, dim1])[i, j, k] = <i, j|A|k>
with A being the stinespring operator, the channel acts as rho -> Tr_2 A rho A^dagger.
Args:
state: input quantum state
stinespring_mat: Stinespring representation for the channel
qubit_idx: which qubits the channel acts on
dtype: data dtype
backend: data backend
Returns:
paddle_quantum.State: output from the channel
"""
qubit_idx = qubit_idx if isinstance(qubit_idx, list) else [qubit_idx]
def genSwapList(origin: List[int], target: List[int]) -> List[Tuple[int, int]]:
assert len(origin) == len(target)
swapped = [False] * len(origin)
swap_ops = []
origin_pos_dict = {v: pos for pos, v in enumerate(origin)}
def positionOfValueAt(idx):
# return the position of value `target[idx]` in origin array
return origin_pos_dict[target[idx]]
ref = origin.copy()
for idx in range(len(origin)):
if not swapped[idx]:
next_idx = idx
swapped[next_idx] = True
while not swapped[positionOfValueAt(next_idx)]:
swapped[positionOfValueAt(next_idx)] = True
if next_idx < positionOfValueAt(next_idx):
swap_ops.append((next_idx, positionOfValueAt(next_idx)))
else:
swap_ops.append((positionOfValueAt(next_idx), next_idx))
x, y = swap_ops[-1]
ref[x], ref[y] = ref[y], ref[x]
# print(idx, (x,y), ref)
next_idx = positionOfValueAt(next_idx)
return swap_ops
num_qubits = state.num_qubits
num_acted_qubits = len(qubit_idx)
dim_ancilla = stinespring_mat.shape[0] // (2 ** num_acted_qubits)
dim_main = 2 ** num_acted_qubits
dim_extended = dim_ancilla * 2 ** num_qubits
# transpose the stinespring matrix such that it has the shape of (dim_ancilla, dim_main, dim_main)
# assuming the input form is (dim_main * dim_ancilla, dim_main)
stine_m = stinespring_mat.reshape([dim_main, dim_ancilla, dim_main]).transpose([1, 0, 2]).reshape(
[dim_main * dim_ancilla, dim_main])
# rotate the density matrix such that the acted_qubits are at the head
state_data = state.data
higher_dims = state_data.shape[:-2]
num_higher_dims = len(higher_dims)
swap_ops = genSwapList(list(range(num_qubits)), qubit_idx + [x for x in range(num_qubits) if x not in qubit_idx])
# make swap for left
for swap_op in swap_ops:
shape = higher_dims.copy()
last_idx = -1
for idx in swap_op:
shape.append(2 ** (idx - last_idx - 1))
shape.append(2)
last_idx = idx
shape.append(2 ** (2 * num_qubits - last_idx - 1))
state_data = paddle.reshape(state_data, shape)
state_data = paddle.transpose(
state_data, list(range(num_higher_dims)) + [item + num_higher_dims for item in [0, 3, 2, 1, 4]]
)
# make swap for right
for swap_op in swap_ops:
shape = higher_dims.copy()
last_idx = -1
shape.append(2 ** num_qubits)
for idx in swap_op:
shape.append(2 ** (idx - last_idx - 1))
shape.append(2)
last_idx = idx
shape.append(2 ** (num_qubits - last_idx - 1))
state_data = paddle.reshape(state_data, shape)
state_data = paddle.transpose(
state_data, list(range(num_higher_dims)) + [item + num_higher_dims for item in [0, 1, 4, 3, 2, 5]]
)
# multiply the stinespring matrix
state_data = paddle.reshape(
state_data, higher_dims.copy() + [dim_main, -1]
)
state_data = paddle.reshape(
paddle.matmul(
stine_m, state_data
), higher_dims.copy() + [dim_extended, 2 ** num_qubits]
)
state_data = paddle.reshape(
state_data, higher_dims.copy() + [dim_extended, dim_main, -1]
)
state_data = paddle.reshape(
paddle.matmul(
stine_m.conj(), state_data
), higher_dims.copy() + [dim_extended, dim_extended]
)
# make partial trace
state_data = paddle.trace(
paddle.reshape(
state_data,
higher_dims.copy() + [dim_ancilla, 2 ** num_qubits, dim_ancilla, 2 ** num_qubits]
),
axis1=len(higher_dims),
axis2=2 + len(higher_dims)
)
# swap back
revert_swap_ops = genSwapList(qubit_idx + [x for x in range(num_qubits) if x not in qubit_idx],
list(range(num_qubits)))
for swap_op in revert_swap_ops:
shape = higher_dims.copy()
last_idx = -1
for idx in swap_op:
shape.append(2 ** (idx - last_idx - 1))
shape.append(2)
last_idx = idx
shape.append(2 ** (2 * num_qubits - last_idx - 1))
state_data = paddle.reshape(state_data, shape)
state_data = paddle.transpose(
state_data, list(range(num_higher_dims)) + [item + num_higher_dims for item in [0, 3, 2, 1, 4]]
)
for swap_op in revert_swap_ops:
shape = higher_dims.copy()
last_idx = -1
shape.append(2 ** num_qubits)
for idx in swap_op:
shape.append(2 ** (idx - last_idx - 1))
shape.append(2)
last_idx = idx
shape.append(2 ** (num_qubits - last_idx - 1))
state_data = paddle.reshape(state_data, shape)
state_data = paddle.transpose(
state_data, list(range(num_higher_dims)) + [item + num_higher_dims for item in [0, 1, 4, 3, 2, 5]]
)
state_data = paddle.reshape(state_data, higher_dims.copy() + [2 ** num_qubits, 2 ** num_qubits])
return paddle_quantum.State(state_data, dtype=dtype, backend=backend)
...@@ -20,7 +20,7 @@ The module of the quantum gates. ...@@ -20,7 +20,7 @@ The module of the quantum gates.
from . import functional from . import functional
from .base import Gate, ParamGate from .base import Gate, ParamGate
from .clifford import Clifford, compose_clifford_circuit from .clifford import Clifford, compose_clifford_circuit
from .single_qubit_gate import H, S, T, X, Y, Z, P, RX, RY, RZ, U3 from .single_qubit_gate import H, S, Sdg, T, Tdg, X, Y, Z, P, RX, RY, RZ, U3
from .multi_qubit_gate import CNOT, CX, CY, CZ, SWAP from .multi_qubit_gate import CNOT, CX, CY, CZ, SWAP
from .multi_qubit_gate import CP, CRX, CRY, CRZ, CU, RXX, RYY, RZZ from .multi_qubit_gate import CP, CRX, CRY, CRZ, CU, RXX, RYY, RZZ
from .multi_qubit_gate import MS, CSWAP, Toffoli from .multi_qubit_gate import MS, CSWAP, Toffoli
......
...@@ -19,10 +19,13 @@ The source file of the basic class for the quantum gates. ...@@ -19,10 +19,13 @@ The source file of the basic class for the quantum gates.
import paddle import paddle
import paddle_quantum import paddle_quantum
from typing import Union, List, Iterable from typing import Union, List, Iterable, Optional, Any
from paddle_quantum.gate.functional.single_qubit_gate import x
from ..intrinsic import _get_float_dtype from ..intrinsic import _get_float_dtype
from math import pi from math import pi
from .functional.visual import _base_gate_display, _base_param_gate_display
import matplotlib
class Gate(paddle_quantum.Operator): class Gate(paddle_quantum.Operator):
r"""Base class for quantum gates. r"""Base class for quantum gates.
...@@ -40,8 +43,12 @@ class Gate(paddle_quantum.Operator): ...@@ -40,8 +43,12 @@ class Gate(paddle_quantum.Operator):
): ):
super().__init__(backend, dtype, name_scope) super().__init__(backend, dtype, name_scope)
self.depth = depth self.depth = depth
self.gate_name = None self.gate_info = {
'gatename': None,
'texname': None,
'plot_width': None,
}
def forward(self, *inputs, **kwargs): def forward(self, *inputs, **kwargs):
raise NotImplementedError raise NotImplementedError
...@@ -58,11 +65,34 @@ class Gate(paddle_quantum.Operator): ...@@ -58,11 +65,34 @@ class Gate(paddle_quantum.Operator):
""" """
gate_history = [] gate_history = []
for _ in range(0, self.depth): for _ in range(self.depth):
for qubit_idx in self.qubits_idx: for qubit_idx in self.qubits_idx:
gate_info = {'gate': self.gate_name, 'which_qubits': qubit_idx, 'theta': None} gate_info = {'gate': self.gate_info['gatename'], 'which_qubits': qubit_idx, 'theta': None}
gate_history.append(gate_info) gate_history.append(gate_info)
self.gate_history = gate_history self.gate_history = gate_history
def set_gate_info(self, **kwargs: Any) -> None:
r'''the interface to set `self.gate_info`
Args:
kwargs: parameters to set `self.gate_info`
'''
self.gate_info.update(kwargs)
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function called by circuit instance when plotting.
Args:
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
Note:
Users could overload this function for custom display.
'''
return _base_gate_display(self, ax, x)
class ParamGate(Gate): class ParamGate(Gate):
...@@ -88,7 +118,7 @@ class ParamGate(Gate): ...@@ -88,7 +118,7 @@ class ParamGate(Gate):
""" """
float_dtype = _get_float_dtype(self.dtype) float_dtype = _get_float_dtype(self.dtype)
if param is None: if param is None:
theta = self.create_parameter( theta = self.create_parameter(
shape=param_shape, dtype=float_dtype, shape=param_shape, dtype=float_dtype,
...@@ -97,16 +127,16 @@ class ParamGate(Gate): ...@@ -97,16 +127,16 @@ class ParamGate(Gate):
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
elif isinstance(param, paddle.fluid.framework.ParamBase): elif isinstance(param, paddle.fluid.framework.ParamBase):
assert param.shape == param_shape, "received: " + str(param.shape) + " expect: " + str(param_shape) assert param.shape == param_shape, f"received: {str(param.shape)} expect: {param_shape}"
self.add_parameter('theta', param) self.add_parameter('theta', param)
elif isinstance(param, paddle.Tensor): elif isinstance(param, paddle.Tensor):
param = param.reshape(param_shape) param = param.reshape(param_shape)
self.theta = param self.theta = param
elif isinstance(param, float): elif isinstance(param, (int, float)):
self.theta = paddle.ones(param_shape, dtype=float_dtype) * param self.theta = paddle.ones(param_shape, dtype=float_dtype) * param
else: # when param is a list of float else: # when param is a list of float
self.theta = paddle.to_tensor(param, dtype=float_dtype).reshape(param_shape) self.theta = paddle.to_tensor(param, dtype=float_dtype).reshape(param_shape)
...@@ -115,12 +145,27 @@ class ParamGate(Gate): ...@@ -115,12 +145,27 @@ class ParamGate(Gate):
""" """
gate_history = [] gate_history = []
for depth_idx in range(0, self.depth): for depth_idx in range(self.depth):
for idx, qubit_idx in enumerate(self.qubits_idx): for idx, qubit_idx in enumerate(self.qubits_idx):
if self.param_sharing: if self.param_sharing:
param = self.theta[depth_idx] param = self.theta[depth_idx]
else: else:
param = self.theta[depth_idx][idx] param = self.theta[depth_idx][idx]
gate_info = {'gate': self.gate_name, 'which_qubits': qubit_idx, 'theta': param} gate_info = {'gate': self.gate_info['gatename'], 'which_qubits': qubit_idx, 'theta': param}
gate_history.append(gate_info) gate_history.append(gate_info)
self.gate_history = gate_history self.gate_history = gate_history
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function called by circuit instance when plotting.
Argrs:
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
Note:
Users could overload this function for custom display.
'''
return _base_param_gate_display(self, ax, x)
\ No newline at end of file
...@@ -18,13 +18,16 @@ The source file of the oracle class and the control oracle class. ...@@ -18,13 +18,16 @@ The source file of the oracle class and the control oracle class.
""" """
import math import math
import matplotlib
import paddle import paddle
import paddle_quantum
import warnings
import paddle_quantum as pq
from . import functional from . import functional
from .base import Gate from .base import Gate
from paddle_quantum.intrinsic import _format_qubits_idx from ..intrinsic import _format_qubits_idx
from typing import Union, Iterable from typing import Union, Iterable
from paddle_quantum.linalg import is_unitary from .functional.visual import _c_oracle_like_display, _oracle_like_display
class Oracle(Gate): class Oracle(Gate):
...@@ -38,22 +41,38 @@ class Oracle(Gate): ...@@ -38,22 +41,38 @@ class Oracle(Gate):
""" """
def __init__( def __init__(
self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int], self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int],
num_qubits: int = None, depth: int = 1, gate_name: str = 'O' num_qubits: int = None, depth: int = 1, gate_info: dict = None
): ):
super().__init__(depth) super().__init__(depth)
oracle = oracle.cast(paddle_quantum.get_dtype()) complex_dtype = pq.get_dtype()
assert is_unitary(oracle), "the input oracle must be a unitary matrix" oracle = oracle.cast(complex_dtype)
num_acted_qubits = int(math.log2(oracle.shape[0]))
self.oracle = paddle.cast(oracle, paddle_quantum.get_dtype()) dimension = oracle.shape[0]
err = paddle.norm(paddle.abs(oracle @ paddle.conj(oracle.T) - paddle.cast(paddle.eye(dimension), complex_dtype))).item()
if err > min(1e-6 * dimension, 0.01):
warnings.warn(
f"\nThe input oracle may not be a unitary: norm(U * U^d - I) = {err}.", UserWarning)
num_acted_qubits = int(math.log2(dimension))
self.oracle = oracle
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits)
self.gate_name = gate_name
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: self.gate_info = {
for _ in range(0, self.depth): 'gatename': 'O',
'texname': r'$O$',
'plot_width': 0.6,
}
if gate_info:
self.gate_info.update(gate_info)
def forward(self, state: pq.State) -> pq.State:
for _ in range(self.depth):
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
state = functional.oracle(state, self.oracle, qubits_idx, self.backend) state = functional.oracle(state, self.oracle, qubits_idx, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _oracle_like_display(self, ax, x)
class ControlOracle(Gate): class ControlOracle(Gate):
...@@ -67,27 +86,40 @@ class ControlOracle(Gate): ...@@ -67,27 +86,40 @@ class ControlOracle(Gate):
""" """
def __init__( def __init__(
self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int]], self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int]],
num_qubits: int = None, depth: int = 1, gate_name: str = 'cO' num_qubits: int = None, depth: int = 1, gate_info: dict = None
) -> None: ) -> None:
super().__init__(depth) super().__init__(depth)
complex_dtype = paddle_quantum.get_dtype() complex_dtype = pq.get_dtype()
oracle = oracle.cast(complex_dtype) oracle = oracle.cast(complex_dtype)
assert is_unitary(oracle), "the input oracle must be a unitary matrix"
num_acted_qubits = int(math.log2(oracle.shape[0])) dimension = oracle.shape[0]
# 暂时只支持单控制位 err = paddle.norm(paddle.abs(oracle @ paddle.conj(oracle.T) - paddle.cast(paddle.eye(dimension), complex_dtype))).item()
if err > min(1e-6 * dimension, 0.01):
warnings.warn(
f"\nThe input oracle may not be a unitary: norm(U * U^d - I) = {err}.", UserWarning)
num_acted_qubits = int(math.log2(dimension))
oracle = ( oracle = (
paddle.kron(paddle.to_tensor([[1.0, 0], [0, 0]], dtype=complex_dtype), paddle.eye(2 ** num_acted_qubits)) + paddle.kron(paddle.to_tensor([[1.0, 0], [0, 0]], dtype=complex_dtype), paddle.eye(2 ** num_acted_qubits)) +
paddle.kron(paddle.to_tensor([[0.0, 0], [0, 1]], dtype=complex_dtype), oracle) paddle.kron(paddle.to_tensor([[0.0, 0], [0, 1]], dtype=complex_dtype), oracle)
) )
num_acted_qubits = num_acted_qubits + 1 num_acted_qubits += 1
self.oracle = oracle self.oracle = oracle
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits)
self.gate_name = gate_name
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: self.gate_info = {
for _ in range(0, self.depth): 'gatename': 'cO',
'texname': r'$O$',
'plot_width': 0.6,
}
if gate_info:
self.gate_info.update(gate_info)
def forward(self, state: pq.State) -> pq.State:
for _ in range(self.depth):
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
state = functional.oracle(state, self.oracle, qubits_idx, self.backend) state = functional.oracle(state, self.oracle, qubits_idx, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _c_oracle_like_display(self, ax, x)
...@@ -42,7 +42,7 @@ class BasisEncoding(Gate): ...@@ -42,7 +42,7 @@ class BasisEncoding(Gate):
super().__init__() super().__init__()
self.num_qubits = num_qubits self.num_qubits = num_qubits
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'BasisEnc' self.gate_info['gatename'] = 'BasisEnc'
def forward(self, feature: paddle.Tensor, state: 'paddle_quantum.State' = None) -> 'paddle_quantum.State': def forward(self, feature: paddle.Tensor, state: 'paddle_quantum.State' = None) -> 'paddle_quantum.State':
if state is None: if state is None:
...@@ -78,7 +78,7 @@ class AmplitudeEncoding(Gate): ...@@ -78,7 +78,7 @@ class AmplitudeEncoding(Gate):
super().__init__() super().__init__()
self.num_qubits = num_qubits self.num_qubits = num_qubits
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'AmpEnc' self.gate_info['gatename'] = 'AmpEnc'
def forward(self, feature: paddle.Tensor) -> 'paddle_quantum.State': def forward(self, feature: paddle.Tensor) -> 'paddle_quantum.State':
def calc_location(location_of_bits_list): def calc_location(location_of_bits_list):
...@@ -157,7 +157,7 @@ class AngleEncoding(Gate): ...@@ -157,7 +157,7 @@ class AngleEncoding(Gate):
feature = paddle.flatten(feature) feature = paddle.flatten(feature)
self.feature = feature self.feature = feature
self.gate_name = 'AngleEnc' self.gate_info['gatename'] = 'AngleEnc'
def forward( def forward(
self, state: 'paddle_quantum.State' = None, invert: bool = False self, state: 'paddle_quantum.State' = None, invert: bool = False
...@@ -206,7 +206,7 @@ class IQPEncoding(Gate): ...@@ -206,7 +206,7 @@ class IQPEncoding(Gate):
feature = paddle.flatten(feature) feature = paddle.flatten(feature)
self.feature = feature self.feature = feature
self.gate_name = 'IQPEnc' self.gate_info['gatename'] = 'IQPEnc'
def forward( def forward(
self, state: paddle_quantum.State = None, invert: Optional[bool] = False self, state: paddle_quantum.State = None, invert: Optional[bool] = False
......
...@@ -19,7 +19,9 @@ The module that contains the functions of various quantum gates. ...@@ -19,7 +19,9 @@ The module that contains the functions of various quantum gates.
from .single_qubit_gate import h from .single_qubit_gate import h
from .single_qubit_gate import s from .single_qubit_gate import s
from .single_qubit_gate import sdg
from .single_qubit_gate import t from .single_qubit_gate import t
from .single_qubit_gate import tdg
from .single_qubit_gate import x from .single_qubit_gate import x
from .single_qubit_gate import y from .single_qubit_gate import y
from .single_qubit_gate import z from .single_qubit_gate import z
......
...@@ -71,6 +71,29 @@ def s(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_q ...@@ -71,6 +71,29 @@ def s(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_q
return transformed_state return transformed_state
def sdg(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
r"""Apply an S dagger (inverse S) gate on the input state.
Args:
state: Input state.
qubit_idx: Index of the qubit on which the gate is applied.
dtype: Type of data.
backend: Backend on which the simulation is run.
Returns:
Output state.
"""
gate = [
[1, 0],
[0, -1j],
]
gate = paddle.to_tensor(gate, dtype=dtype)
state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
transformed_state = state.clone()
transformed_state.data = state_data
return transformed_state
def t(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State: def t(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
r"""Apply a T gate on the input state. r"""Apply a T gate on the input state.
...@@ -93,6 +116,27 @@ def t(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_q ...@@ -93,6 +116,27 @@ def t(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_q
transformed_state.data = state_data transformed_state.data = state_data
return transformed_state return transformed_state
def tdg(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
r"""Apply a T dagger (inverse T) gate on the input state.
Args:
state: Input state.
qubit_idx: Index of the qubit on which the gate is applied.
dtype: Type of data.
backend: Backend on which the simulation is run.
Returns:
Output state.
"""
gate = [
[1, 0],
[0, math.cos(math.pi/4) - math.sin(math.pi/4) * 1j],
]
gate = paddle.to_tensor(gate, dtype=dtype)
state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
transformed_state = state.clone()
transformed_state.data = state_data
return transformed_state
def x(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State: def x(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_quantum.Backend) -> paddle_quantum.State:
r"""Apply an X gate on the input state. r"""Apply an X gate on the input state.
......
# !/usr/bin/env python3
# Copyright (c) 2021 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"""
The visualization function of ``paddle_quantum.gate.Gate`` for display
in ``paddle_quantum.ansatz.Circuit`` .
"""
from typing import Optional, Union, List, Any
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from matplotlib.patches import Circle, Rectangle
import paddle_quantum as pq
# the default parameters of plot
__CIRCUIT_PLOT_PARAM = {
'scale': 1.0, # scale flag
'circuit_height': 0.55, # the height of per line
'offset': 0.12, # the head and tail offset of horizontal circuit lines
'line_width': 0.8,
'block_margin': 0.07, # the horizontal and vertical margin of a rectangle
'node_width': 0.2, # the block width of patches
'fontsize': 9.5,
'circle_radius': 0.08,
'cross_radius': 0.06,
'dot_radius': 0.03,
'margin': 0.02, # the proportion of figure margin
'tex': False,
}
def scale_circuit_plot_param(scale: float) -> None:
r'''The scale function of ``__CIRCUIT_PLOT_PARAM`` dictionary
Args:
scale: the scalar for scaling the elements in the figure
'''
for key in __CIRCUIT_PLOT_PARAM:
if isinstance(__CIRCUIT_PLOT_PARAM[key], (int, float)):
__CIRCUIT_PLOT_PARAM[key] = scale * __CIRCUIT_PLOT_PARAM[key]
def set_circuit_plot_param(**kwargs: Any) -> None:
r'''The set function of ``__CIRCUIT_PLOT_PARAM`` dictionary
Args:
kwargs: parameters to update the ``__CIRCUIT_PLOT_PARAM`` dictionary
'''
__CIRCUIT_PLOT_PARAM.update(kwargs)
def get_circuit_plot_param() -> dict:
r'''The output function of ``__CIRCUIT_PLOT_PARAM`` dictionary
Returns:
a copy of ``__CIRCUIT_PLOT_PARAM``
'''
return __CIRCUIT_PLOT_PARAM.copy()
def reset_circuit_plot_param() -> None:
r'''The reset function of ``__CIRCUIT_PLOT_PARAM`` dictionary
'''
global __CIRCUIT_PLOT_PARAM
__CIRCUIT_PLOT_PARAM = {
'scale': 1.0,
'circuit_height': 0.55,
'offset': 0.12,
'line_width': 0.8,
'block_margin': 0.07,
'node_width': 0.2,
'fontsize': 9.5,
'circle_radius': 0.08,
'cross_radius': 0.06,
'dot_radius': 0.03,
'margin': 0.02,
'tex': False,
}
def _circuit_plot(
circuit,
dpi: Optional[int] = 100,
scale: Optional[float] = 1.0,
tex: Optional[bool] = False,
) -> Union[None, matplotlib.figure.Figure]:
r'''display the circuit using matplotlib
Args:
save_path: the save path of image
dpi: dots per inches, here is resolution ratio
show: whether execute ``plt.show()``
output: whether return the ``matplotlib.figure.Figure`` instance
scale: scale coefficient of figure
Returns:
a ``matplotlib.figure.Figure`` instance or ``None`` depends on ``output``
Note:
Using ``plt.show()`` may cause a distortion, but it will not happen in the figure saved.
If the depth is too long, there will be some patches unable to display.
'''
if scale != __CIRCUIT_PLOT_PARAM['scale']: # scale
scale_circuit_plot_param(scale)
set_circuit_plot_param(tex=tex)
height = __CIRCUIT_PLOT_PARAM['circuit_height']
lw = __CIRCUIT_PLOT_PARAM['line_width']
offset = __CIRCUIT_PLOT_PARAM['offset']
margin = __CIRCUIT_PLOT_PARAM['margin']
_x = 0 # initial postion of horizontal
_y = 0 # initial postion of vertical
_fig = plt.figure(facecolor='w', edgecolor='w', dpi=dpi)
_axes = _fig.add_subplot(1, 1, 1,)
for gate in circuit.sublayers():
if not isinstance(gate, pq.gate.Gate):
raise NotImplementedError
_x += gate.display_in_circuit(_axes, _x)
for _ in range(circuit.num_qubits): # plot horizontal lines for all qubits
line = Line2D((-offset, _x + offset), (_y, _y), lw=lw, color='k', zorder=0)
_axes.add_line(line)
_y += height
# set the figure size and pattern
_axes.set_axis_off()
_axes.set_xlim(- offset, _x + offset)
_axes.set_ylim(_y - height * 0.5, - height * 0.5)
_axes.set_aspect('equal')
_fig.set_figwidth(_x + offset * 2)
_fig.set_figheight((circuit.num_qubits)*height)
plt.subplots_adjust(top=1-margin, bottom=margin, right=1-margin, left=margin,)
return _fig
def _single_qubit_gate_display(ax: matplotlib.axes.Axes, x: float, y: float, h: float, w: float,
tex_name: Optional[str] = None,) -> None:
r'''Add a rectangle gate with name.
Args:
ax: matplotlib.axes.Axes instance
x: the start horizontal position in the figure
y: the center of vertical postion
h: height per line
w: width for one block
tex_name: the name written in latex to print
'''
margin = __CIRCUIT_PLOT_PARAM['block_margin']
fontsize = __CIRCUIT_PLOT_PARAM['fontsize']
lw = __CIRCUIT_PLOT_PARAM['line_width']
tex_ = __CIRCUIT_PLOT_PARAM['tex']
rect = Rectangle((x+margin, y-h*0.5+margin), height=h-margin*2, width=w-margin*2,
lw=lw, fc='w', ec='k', zorder=1)
ax.add_patch(rect)
if tex_name is not None:
ax.text(x+w*0.5, y, s=tex_name, size=fontsize, zorder=2,
color='k', ha='center', va='center', rasterized=True, usetex=tex_,)
def _multiple_qubit_gate_display(ax: matplotlib.axes.Axes, x: float, y1: float, y2: float, h: float, w: float,
tex_name: Optional[str] = None,) -> None:
r'''Add a rectangle gate acting on multiple continuous gates with name.
Args:
ax: matplotlib.axes.Axes instance
x: the start horizontal position in the figure
y1: the center of vertical postion of minimum qubits
y2: the center of vertical postion of maximum qubits
h: height per line
w: width for one block
tex_name: the name written in latex to print
'''
margin = __CIRCUIT_PLOT_PARAM['block_margin']
fontsize = __CIRCUIT_PLOT_PARAM['fontsize']
lw = __CIRCUIT_PLOT_PARAM['line_width']
tex_ = __CIRCUIT_PLOT_PARAM['tex']
total_h = y2-y1+h
rect = Rectangle((x+margin, y1-h*0.5+margin),
height=total_h-margin*2, width=w-2*margin, fc='w', ec='k', lw=lw, zorder=1)
ax.add_patch(rect)
ax.text(x+w*0.5, (y1+y2)*0.5, s=tex_name, size=fontsize,
color='k', ha='center', va='center', rasterized=True, usetex=tex_)
def _patch_display(ax: matplotlib.axes.Axes, x: float, y: float, mode: str,) -> None:
r'''Add a patch
Args:
ax: matplotlib.axes.Axes instance
x: the central horizontal position in the block
y: the center of vertical postion
mode: '+' is for controlled object, 'x' is a cross used `SWAP`, '.' is for controlling
'''
lw = __CIRCUIT_PLOT_PARAM['line_width']
if mode == '+':
crcl_r = __CIRCUIT_PLOT_PARAM['circle_radius']
reverse_dot = Circle((x, y), crcl_r, fc='none', ec='k', lw=lw, zorder=1)
line_v = Line2D((x, x), (y-crcl_r, y+crcl_r), zorder=1, c='k', lw=lw,) # verticle line
ax.add_patch(reverse_dot)
ax.add_line(line_v)
elif mode == '.':
dot_r = __CIRCUIT_PLOT_PARAM['dot_radius']
dot = Circle((x, y), dot_r, fc='k', ec='k', lw=lw, zorder=1)
ax.add_patch(dot)
elif mode == 'x':
crs_r = __CIRCUIT_PLOT_PARAM['cross_radius']
line_left = Line2D((x-crs_r, x+crs_r), (y-crs_r, y+crs_r), c='k', lw=lw, zorder=1)
line_right = Line2D((x-crs_r, x+crs_r), (y+crs_r, y-crs_r), c='k', lw=lw, zorder=1)
ax.add_line(line_left)
ax.add_line(line_right)
def _vertical_line_display(ax: matplotlib.axes.Axes, x: float, y1: float, y2: float,) -> None:
r'''Add a patch
Args:
ax: matplotlib.axes.Axes instance
x: the central horizontal position in the block
y1: the vertical postion of one end
y2: the vertical postion of the other end
'''
lw = __CIRCUIT_PLOT_PARAM['line_width']
line_ = Line2D((x, x), (y1, y2), lw=lw, c='k', zorder=0)
ax.add_line(line_)
def _param_tex_name_(tex_name: str, theta: Union[float, List[float]]) -> str:
r'''Combine latex name and its parameters
Args:
tex_name: latex name
theta: parameters to plot
'''
if isinstance(theta, float):
return f'{tex_name[:-1]}(' + f'{theta:.2f}' + ')$'
param = ''.join(f'{float(value):.2f},' for value in theta)
return f'{tex_name[:-1]}({param[:-1]})$'
def _is_continuous_list(qubits_idx: List[int]) -> bool:
r'''Check whether the list is continuous
Args:
qubits_idx: a list with different elements
'''
return len(qubits_idx) == max(qubits_idx) - min(qubits_idx) + 1
def _not_exist_intersection(list_a: List[float], list_b: List[float]) -> bool:
r'''Check whether there is an overlap in ``List_a`` and ``List_b``.
Args:
List_a: a list with two elements
List_b: a list with two elements
'''
min_a = min(list_a)
max_a = max(list_a)
min_b = min(list_b)
max_b = max(list_b)
min_ab = min(min_a, min_b)
max_ab = max(max_a, max_b)
return max_a+max_b-min_a-min_b < max_ab-min_ab
def _base_gate_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for single qubit base gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
tex_name = gate.gate_info['texname']
for _ in range(gate.depth):
for act_qubits in gate.qubits_idx: # get vertical position
_single_qubit_gate_display(ax, x, act_qubits*h, h, w, tex_name)
x += w # next layer
return x - x_start
def _base_param_gate_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for single qubit paramgate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
tex_name = gate.gate_info['texname']
for depth in range(gate.depth):
for param_idx, act_qubits in enumerate(gate.qubits_idx):
if gate.param_sharing: # get parameters depending on this flag
theta = gate.theta[depth]
else:
theta = gate.theta[depth, param_idx]
_single_qubit_gate_display(ax, x, act_qubits*h, h, w, _param_tex_name_(tex_name, theta))
x += w
return x - x_start
def _cx_like_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``cx`` like gate .
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
tex_name = gate.gate_info['texname']
for _ in range(gate.depth):
for act_qubits in gate.qubits_idx:
x_c = x + 0.5 * w # the center of block
_patch_display(ax, x_c, act_qubits[0]*h, mode='.')
_single_qubit_gate_display(ax, x, act_qubits[1]*h, h, w, tex_name)
_vertical_line_display(ax, x_c, act_qubits[0]*h, act_qubits[1]*h)
x += w
return x - x_start
def _crx_like_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``crx`` like gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
tex_name = gate.gate_info['texname']
for depth in range(gate.depth):
for param_idx, act_qubits in enumerate(gate.qubits_idx):
if gate.param_sharing:
theta = gate.theta[depth]
else:
theta = gate.theta[depth, param_idx]
x_c = x + 0.5 * w # the center of block
_patch_display(ax, x_c, act_qubits[0]*h, mode='.')
_single_qubit_gate_display(ax, x, act_qubits[1]*h, h, w, _param_tex_name_(tex_name, theta))
_vertical_line_display(ax, x_c, act_qubits[0]*h, act_qubits[1]*h)
x += w
return x - x_start
def _oracle_like_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``oracle`` like gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
tex_name = gate.gate_info['texname']
for _ in range(gate.depth):
for act_qubits in gate.qubits_idx:
if isinstance(act_qubits, (int, float)):
_single_qubit_gate_display(ax, x, act_qubits*h, h, w, tex_name)
else:
assert _is_continuous_list(act_qubits), 'Discontinuous oracle cannot be plotted.'
_multiple_qubit_gate_display(ax, x, min(act_qubits)*h, max(act_qubits)*h, h, w, tex_name)
x += w
return x - x_start
def _c_oracle_like_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``control oracle`` like gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
tex_name = gate.gate_info['texname']
for _ in range(gate.depth):
for act_qubits in gate.qubits_idx:
assert _is_continuous_list(act_qubits[1:]), 'Discontinuous oracle cannot be plotted.'
min_ = min(act_qubits[1:])
max_ = max(act_qubits[1:])
if act_qubits[0] <= max_ and act_qubits[0] >= min_:
raise RuntimeError('Invalid input of control oracle. ')
x_c = x + 0.5 * w
_patch_display(ax, x_c, h*act_qubits[0], mode='.')
_multiple_qubit_gate_display(ax, x, min_*h, max_*h, h, w, tex_name)
_vertical_line_display(ax, x_c, h*act_qubits[0], 0.5*(min_*h+max_*h))
x += w
return x - x_start
def _rxx_like_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``rxx`` like gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
tex_name = gate.gate_info['texname']
for depth in range(gate.depth):
for param_idx, act_qubits in enumerate(gate.qubits_idx):
assert _is_continuous_list(act_qubits), 'Discontinuous oracle cannot be plotted.'
if gate.param_sharing:
theta = gate.theta[depth]
else:
theta = gate.theta[depth, param_idx]
_multiple_qubit_gate_display(ax, x, min(act_qubits)*h, max(act_qubits)*h,
h, w, _param_tex_name_(tex_name, theta))
x += w
return x - x_start
def _cnot_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``cnot`` like gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
for _ in range(gate.depth):
for act_qubits in gate.qubits_idx:
x_c = x + 0.5 * w
_patch_display(ax, x_c, act_qubits[0]*h, mode='.')
_patch_display(ax, x_c, act_qubits[1]*h, mode='+')
_vertical_line_display(ax, x_c, act_qubits[0]*h, act_qubits[1]*h)
x += w
return x - x_start
def _swap_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``swap`` like gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
for _ in range(gate.depth):
for act_qubits in gate.qubits_idx:
x_c = x + 0.5 * w
_patch_display(ax, x_c, act_qubits[0]*h, mode='x')
_patch_display(ax, x_c, act_qubits[1]*h, mode='x')
_vertical_line_display(ax, x_c, act_qubits[0]*h, act_qubits[1]*h)
x += w
return x - x_start
def _cswap_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``cswap`` like gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
for _ in range(gate.depth):
for act_qubits in gate.qubits_idx:
x_c = x + 0.5 * w
_patch_display(ax, x_c, act_qubits[0]*h, mode='.')
_patch_display(ax, x_c, act_qubits[1]*h, mode='x')
_patch_display(ax, x_c, act_qubits[2]*h, mode='x')
_vertical_line_display(ax, x_c, min(act_qubits)*h, max(act_qubits)*h)
x += w
return x - x_start
def _tofolli_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``tofolli`` like gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
for _ in range(gate.depth):
for act_qubits in gate.qubits_idx:
x_c = x + 0.5 * w
_patch_display(ax, x_c, act_qubits[0]*h, mode='.')
_patch_display(ax, x_c, act_qubits[1]*h, mode='.')
_patch_display(ax, x_c, act_qubits[2]*h, mode='+')
_vertical_line_display(ax, x_c, min(act_qubits)*h, max(act_qubits)*h)
x += w
return x - x_start
def _linear_entangled_layer_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``linear entangled layer`` gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
block_w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
dot_w = __CIRCUIT_PLOT_PARAM['node_width']
for depth in range(gate.depth):
for param_idx, act_qubits in enumerate(gate.qubits_idx): # `ry` layer
_single_qubit_gate_display(ax, x, act_qubits*h, h, block_w,
f'$R_y({float(gate.theta[depth][param_idx][0]):.2f})$')
x += block_w
for idx in range(len(gate.qubits_idx)-1): # `cnot` layer
act_qubits = [gate.qubits_idx[idx], gate.qubits_idx[idx + 1]]
x_c = x + 0.5 * dot_w
_patch_display(ax, x_c, act_qubits[0]*h, mode='.')
_patch_display(ax, x_c, act_qubits[1]*h, mode='+')
_vertical_line_display(ax, x_c, act_qubits[0]*h, act_qubits[1]*h)
x += dot_w
for param_idx, act_qubits in enumerate(gate.qubits_idx): # `rz` layer
_single_qubit_gate_display(ax, x, act_qubits*h, h, block_w,
f'$R_z({float(gate.theta[depth][param_idx][1]):.2f})$')
x += block_w
for idx in range(len(gate.qubits_idx)-1): # `cnot` layer
act_qubits = [gate.qubits_idx[idx], gate.qubits_idx[idx + 1]]
x_c = x + 0.5 * dot_w
_patch_display(ax, x_c, act_qubits[0]*h, mode='.')
_patch_display(ax, x_c, act_qubits[1]*h, mode='+')
_vertical_line_display(ax, x_c, act_qubits[0]*h, act_qubits[1]*h)
x += dot_w
return x - x_start
def _cr_entangled_layer_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``complex or real entangled layer`` gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
tex_name = gate.gate_info['texname']
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
block_w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
dot_w = __CIRCUIT_PLOT_PARAM['node_width']
if tex_name == '$REntLayer$': # define the tex name generator for deferent gates
def _name_generator(theta):
return f'$R_y({float(theta):.2f})$'
elif tex_name == '$CEntLayer$':
def _name_generator(theta):
name = '$U('
for value in theta:
name += f'{float(value):.2f},'
name = name.strip(',')
name += ')$'
return name
for depth in range(gate.depth):
for param_idx, act_qubits in enumerate(gate.qubits_idx):
_single_qubit_gate_display(ax, x, act_qubits*h, h, block_w,
_name_generator(gate.theta[depth][param_idx]))
x += block_w
for idx in range(len(gate.qubits_idx)):
act_qubits = [gate.qubits_idx[idx], gate.qubits_idx[(idx + 1) % len(gate.qubits_idx)]]
x_c = x + 0.5 * dot_w
_patch_display(ax, x_c, act_qubits[0]*h, mode='.')
_patch_display(ax, x_c, act_qubits[1]*h, mode='+')
_vertical_line_display(ax, x_c, act_qubits[0]*h, act_qubits[1]*h)
x += dot_w
return x - x_start
def _cr_block_layer_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``complex or real block layer`` gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
block_w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
tex_name = gate.gate_info['texname']
dot_w = __CIRCUIT_PLOT_PARAM['node_width']
if tex_name == '$RBLayer$':
def _add_block(ax, x, theta, pos): # add a block including 4 `ry` and 1 `cnot`
_single_qubit_gate_display(ax, x, pos[0]*h, h, block_w,
tex_name=f'$R_y({float(theta[0]):.2f})$')
_single_qubit_gate_display(ax, x, pos[1]*h, h, block_w,
tex_name=f'$R_y({float(theta[1]):.2f})$')
_single_qubit_gate_display(ax, x+block_w+dot_w, pos[0]*h, h, block_w,
tex_name=f'$R_y({float(theta[2]):.2f})$')
_single_qubit_gate_display(ax, x+block_w+dot_w, pos[1]*h, h, block_w,
tex_name=f'$R_y({float(theta[3]):.2f})$')
_patch_display(ax, x+block_w+0.5*dot_w, pos[0]*h, mode='.')
_patch_display(ax, x+block_w+0.5*dot_w, pos[1]*h, mode='+')
_vertical_line_display(ax, x+block_w+0.5*dot_w, pos[0]*h, pos[1]*h)
elif tex_name == '$CBLayer$':
def _add_block(ax, x, theta, pos,): # add a block including 4 `u3` and 1 `cnot`
_single_qubit_gate_display(ax, x, pos[0]*h, h, block_w,
tex_name=f'$U({float(theta[0]):.2f},\
{float(theta[1]):.2f},{float(theta[2]):.2f})$')
_single_qubit_gate_display(ax, x, pos[1]*h, h, block_w,
tex_name=f'$U({float(theta[3]):.2f},\
{float(theta[4]):.2f},{float(theta[5]):.2f})$')
_single_qubit_gate_display(ax, x+block_w+dot_w, pos[0]*h, h, block_w,
tex_name=f'$U({float(theta[6]):.2f},\
{float(theta[7]):.2f},{float(theta[8]):.2f})$')
_single_qubit_gate_display(ax, x+block_w+dot_w, pos[1]*h, h, block_w,
tex_name=f'$U({float(theta[9]):.2f},\
{float(theta[10]):.2f},{float(theta[11]):.2f})$')
_patch_display(ax, x+block_w+0.5*dot_w, pos[0]*h, mode='.')
_patch_display(ax, x+block_w+0.5*dot_w, pos[1]*h, mode='+')
_vertical_line_display(ax, x+block_w+0.5*dot_w, pos[0]*h, pos[1]*h)
def _get_display_layer(): # arrange the order of block and compress
display_layer = []
num_theta_layer = [0, 0]
act_qubits = gate.qubits_idx
for layer in range(2):
indices = []
for i in range(1, len(act_qubits), 2):
indices.append([act_qubits[i-1], act_qubits[i]])
while len(indices) > 0:
indcs_list = [indices.pop(0)]
num_theta_layer[layer] += 1
for _ in range(len(indices)):
candidate = indices.pop(0)
if all(_not_exist_intersection(indcs, candidate) for indcs in indcs_list):
indcs_list.append(candidate)
else:
indices.append(candidate)
display_layer.append(indcs_list)
act_qubits = act_qubits[1:]
return display_layer, num_theta_layer
def _add_layer(ax, x, theta, pos_list,): # add block into layer
plotted_list = []
for pos in pos_list:
_add_block(ax, x, theta[int((gate.qubits_idx.index(pos[0])) / 2)], pos,)
plotted_list.extend(list(range(min(pos), max(pos)+1)))
display_layer, num_theta_layer = _get_display_layer() # get layers
for depth in range(gate.depth):
for num_theta, layer in enumerate(display_layer):
if num_theta < num_theta_layer[0]: # get parameters
_add_layer(ax, x, gate.theta[depth, :int((len(gate.qubits_idx)) / 2)], layer)
else:
_add_layer(ax, x, gate.theta[depth, int((len(gate.qubits_idx)) / 2):], layer)
x += 2 * block_w + dot_w
return x - x_start
def _qaoa_layer_display(gate, ax: matplotlib.axes.Axes, x: float,) -> float:
r'''The display function for ``qaoa layer`` gate.
Args:
gate: the ``paddle_quantum.gate.Gate`` instance
ax: the ``matplotlib.axes.Axes`` instance
x: the start horizontal position
Returns:
the total width occupied
'''
x_start = x
h = __CIRCUIT_PLOT_PARAM['circuit_height']
block_w = __CIRCUIT_PLOT_PARAM['scale'] * gate.gate_info['plot_width']
dot_w = __CIRCUIT_PLOT_PARAM['node_width']
def _get_display_layer(): # arrange the order of block and compress
indices = gate.edges
display_layer = []
while len(indices) > 0:
indcs_list = [indices.pop(0)]
for _ in range(len(indices)):
candidate = indices.pop(0)
if all(_not_exist_intersection(indcs, candidate) for indcs in indcs_list):
indcs_list.append(candidate)
else:
indices.append(candidate)
display_layer.append(indcs_list)
return display_layer
def _add_block(ax, x, theta, pos): # add a block including 2 `cnot` and 1 `rz`
_single_qubit_gate_display(ax, x+dot_w, pos[1]*h, h, block_w, tex_name=f'$R_z({float(theta):.2f})$')
_patch_display(ax, x+0.5*dot_w, pos[0]*h, mode='.')
_patch_display(ax, x+0.5*dot_w, pos[1]*h, mode='+')
_vertical_line_display(ax, x+0.5*dot_w, pos[0]*h, pos[1]*h)
_patch_display(ax, x+block_w+1.5*dot_w, pos[0]*h, mode='.')
_patch_display(ax, x+block_w+1.5*dot_w, pos[1]*h, mode='+')
_vertical_line_display(ax, x+block_w+1.5*dot_w, pos[0]*h, pos[1]*h)
def _add_layer(axis, x, theta, pos_list): # add block into layer
plotted_list = []
for pos in pos_list:
_add_block(axis, x, theta, pos)
plotted_list.extend(list(range(min(pos), max(pos)+1)))
x_start = x
gamma = gate.theta[:gate.depth]
beta = gate.theta[gate.depth:]
display_layer = _get_display_layer()
for depth in range(gate.depth):
for layer in display_layer:
_add_layer(ax, x, gamma[depth], layer)
x += block_w + 2 * dot_w
for y in gate.nodes:
_single_qubit_gate_display(ax, x, y*h, h, block_w, tex_name=f'$R_x({float(beta[depth]):.2f})$')
x += block_w
return x - x_start
...@@ -25,7 +25,8 @@ from paddle_quantum.gate import functional ...@@ -25,7 +25,8 @@ from paddle_quantum.gate import functional
from paddle_quantum.intrinsic import _get_float_dtype from paddle_quantum.intrinsic import _get_float_dtype
from .base import Gate from .base import Gate
from typing import Iterable, List, Union, Tuple, Dict from typing import Iterable, List, Union, Tuple, Dict
import matplotlib
from .functional.visual import ( _linear_entangled_layer_display, _cr_block_layer_display, _qaoa_layer_display,_cr_entangled_layer_display)
def qubits_idx_filter(qubits_idx: Union[Iterable[int], str], num_qubits: int) -> List[Iterable[int]]: def qubits_idx_filter(qubits_idx: Union[Iterable[int], str], num_qubits: int) -> List[Iterable[int]]:
r"""Check the validity of ``qubits_idx`` and ``num_qubits``. r"""Check the validity of ``qubits_idx`` and ``num_qubits``.
...@@ -64,7 +65,11 @@ class SuperpositionLayer(Gate): ...@@ -64,7 +65,11 @@ class SuperpositionLayer(Gate):
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = qubits_idx_filter(qubits_idx, num_qubits) self.qubits_idx = qubits_idx_filter(qubits_idx, num_qubits)
self.gate_name = 'SupLayer' self.gate_info = {
'gatename': 'SupLayer',
'texname': r'$H$',
'plot_width': 0.4,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for _ in range(0, self.depth): for _ in range(0, self.depth):
...@@ -79,6 +84,9 @@ class SuperpositionLayer(Gate): ...@@ -79,6 +84,9 @@ class SuperpositionLayer(Gate):
gate_info = {'gate': 'h', 'which_qubits': qubit_idx, 'theta': None} gate_info = {'gate': 'h', 'which_qubits': qubit_idx, 'theta': None}
gate_history.append(gate_info) gate_history.append(gate_info)
self.gate_history = gate_history self.gate_history = gate_history
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return super().display_in_circuit(ax, x)
...@@ -95,7 +103,11 @@ class WeakSuperpositionLayer(Gate): ...@@ -95,7 +103,11 @@ class WeakSuperpositionLayer(Gate):
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = qubits_idx_filter(qubits_idx, num_qubits) self.qubits_idx = qubits_idx_filter(qubits_idx, num_qubits)
self.gate_name = 'WSupLayer' self.gate_info = {
'gatename': 'WSupLayer',
'texname': r'$R_y(\pi/4)$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
theta = paddle.to_tensor([np.pi / 4]) theta = paddle.to_tensor([np.pi / 4])
...@@ -112,6 +124,9 @@ class WeakSuperpositionLayer(Gate): ...@@ -112,6 +124,9 @@ class WeakSuperpositionLayer(Gate):
gate_history.append(gate_info) gate_history.append(gate_info)
self.gate_history = gate_history self.gate_history = gate_history
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return super().display_in_circuit(ax, x)
class LinearEntangledLayer(Gate): class LinearEntangledLayer(Gate):
r"""Linear entangled layers consisting of Ry gates, Rz gates, and CNOT gates. r"""Linear entangled layers consisting of Ry gates, Rz gates, and CNOT gates.
...@@ -136,7 +151,11 @@ class LinearEntangledLayer(Gate): ...@@ -136,7 +151,11 @@ class LinearEntangledLayer(Gate):
default_initializer=initializer default_initializer=initializer
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.gate_name = 'LEntLayer' self.gate_info = {
'gatename': 'LEntLayer',
'texname': r'$LEntLayer$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for depth_idx in range(0, self.depth): for depth_idx in range(0, self.depth):
...@@ -171,6 +190,9 @@ class LinearEntangledLayer(Gate): ...@@ -171,6 +190,9 @@ class LinearEntangledLayer(Gate):
gate_history.append({'gate': 'cnot', 'which_qubits': [qubits_idx[idx], qubits_idx[idx + 1]], 'theta': None}) gate_history.append({'gate': 'cnot', 'which_qubits': [qubits_idx[idx], qubits_idx[idx + 1]], 'theta': None})
self.gate_history = gate_history self.gate_history = gate_history
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _linear_entangled_layer_display(self, ax, x)
class RealEntangledLayer(Gate): class RealEntangledLayer(Gate):
r"""Strongly entangled layers consisting of Ry gates and CNOT gates. r"""Strongly entangled layers consisting of Ry gates and CNOT gates.
...@@ -200,7 +222,11 @@ class RealEntangledLayer(Gate): ...@@ -200,7 +222,11 @@ class RealEntangledLayer(Gate):
default_initializer=initializer default_initializer=initializer
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.gate_name = 'REntLayer' self.gate_info = {
'gatename': 'REntLayer',
'texname': r'$REntLayer$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for depth_idx in range(0, self.depth): for depth_idx in range(0, self.depth):
...@@ -227,6 +253,11 @@ class RealEntangledLayer(Gate): ...@@ -227,6 +253,11 @@ class RealEntangledLayer(Gate):
'theta': None 'theta': None
}) })
self.gate_history = gate_history self.gate_history = gate_history
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _cr_entangled_layer_display(self, ax, x)
class ComplexEntangledLayer(Gate): class ComplexEntangledLayer(Gate):
...@@ -256,7 +287,11 @@ class ComplexEntangledLayer(Gate): ...@@ -256,7 +287,11 @@ class ComplexEntangledLayer(Gate):
default_initializer=initializer default_initializer=initializer
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.gate_name = 'CEntLayer' self.gate_info = {
'gatename': 'CEntLayer',
'texname': r'$CEntLayer$',
'plot_width': 1.65,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for depth_idx in range(0, self.depth): for depth_idx in range(0, self.depth):
...@@ -284,6 +319,10 @@ class ComplexEntangledLayer(Gate): ...@@ -284,6 +319,10 @@ class ComplexEntangledLayer(Gate):
}) })
self.gate_history = gate_history self.gate_history = gate_history
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _cr_entangled_layer_display(self, ax, x)
class RealBlockLayer(Gate): class RealBlockLayer(Gate):
r"""Weakly entangled layers consisting of Ry gates and CNOT gates. r"""Weakly entangled layers consisting of Ry gates and CNOT gates.
...@@ -313,7 +352,11 @@ class RealBlockLayer(Gate): ...@@ -313,7 +352,11 @@ class RealBlockLayer(Gate):
default_initializer=initializer default_initializer=initializer
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.gate_name = 'RBLayer' self.gate_info = {
'gatename': 'RBLayer',
'texname': r'$RBLayer$',
'plot_width': 0.9,
}
def __add_real_block(self, theta: paddle.Tensor, position: List[int]) -> None: def __add_real_block(self, theta: paddle.Tensor, position: List[int]) -> None:
assert len(theta) == 4, 'the length of theta is not right' assert len(theta) == 4, 'the length of theta is not right'
...@@ -379,6 +422,9 @@ class RealBlockLayer(Gate): ...@@ -379,6 +422,9 @@ class RealBlockLayer(Gate):
self.__add_real_layer(self.theta[depth_idx, int((n - 1) / 2):], [1, n - 1]) self.__add_real_layer(self.theta[depth_idx, int((n - 1) / 2):], [1, n - 1])
self.count_history = False self.count_history = False
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _cr_block_layer_display(self, ax, x)
class ComplexBlockLayer(Gate): class ComplexBlockLayer(Gate):
r"""Weakly entangled layers consisting of single-qubit rotation gates and CNOT gates. r"""Weakly entangled layers consisting of single-qubit rotation gates and CNOT gates.
...@@ -406,7 +452,11 @@ class ComplexBlockLayer(Gate): ...@@ -406,7 +452,11 @@ class ComplexBlockLayer(Gate):
default_initializer=initializer default_initializer=initializer
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.gate_name = 'CBLayer' self.gate_info = {
'gatename': 'CBLayer',
'texname': r'$CBLayer$',
'plot_width': 1.65,
}
def __add_complex_block(self, theta: paddle.Tensor, position: List[int]) -> None: def __add_complex_block(self, theta: paddle.Tensor, position: List[int]) -> None:
assert len(theta) == 12, 'the length of theta is not right' assert len(theta) == 12, 'the length of theta is not right'
...@@ -474,6 +524,9 @@ class ComplexBlockLayer(Gate): ...@@ -474,6 +524,9 @@ class ComplexBlockLayer(Gate):
self.__add_complex_layer(self.theta[depth_idx, :int((n - 1) / 2)], [0, n - 2]) self.__add_complex_layer(self.theta[depth_idx, :int((n - 1) / 2)], [0, n - 2])
self.__add_complex_layer(self.theta[depth_idx, int((n - 1) / 2):], [1, n - 1]) self.__add_complex_layer(self.theta[depth_idx, int((n - 1) / 2):], [1, n - 1])
self.count_history = False self.count_history = False
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _cr_block_layer_display(self, ax, x)
class QAOALayer(Gate): class QAOALayer(Gate):
...@@ -503,7 +556,11 @@ class QAOALayer(Gate): ...@@ -503,7 +556,11 @@ class QAOALayer(Gate):
default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * math.pi) default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.gate_name = 'QAOALayer' self.gate_info = {
'gatename': 'QAOALayer',
'texname': r'$QAOALayer$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
gamma = self.theta[:self.depth] gamma = self.theta[:self.depth]
...@@ -529,6 +586,8 @@ class QAOALayer(Gate): ...@@ -529,6 +586,8 @@ class QAOALayer(Gate):
for node in self.nodes: for node in self.nodes:
gate_history.append({'gate': 'rx', 'which_qubits': node, 'theta': beta[depth_idx]}) gate_history.append({'gate': 'rx', 'which_qubits': node, 'theta': beta[depth_idx]})
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _qaoa_layer_display(self, ax, x)
class QAOALayerWeighted(Gate): class QAOALayerWeighted(Gate):
r""" QAOA driving layers with weights r""" QAOA driving layers with weights
...@@ -556,6 +615,12 @@ class QAOALayerWeighted(Gate): ...@@ -556,6 +615,12 @@ class QAOALayerWeighted(Gate):
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.gate_history_generation() self.gate_history_generation()
self.gate_info = {
'gatename': 'WQAOALayer',
'texname': r'$WQAOALayer$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
gamma = self.theta[:self.depth] gamma = self.theta[:self.depth]
...@@ -581,4 +646,11 @@ class QAOALayerWeighted(Gate): ...@@ -581,4 +646,11 @@ class QAOALayerWeighted(Gate):
gate_history.append({'gate': 'cnot', 'which_qubits': [node0, node1], 'theta': None}) gate_history.append({'gate': 'cnot', 'which_qubits': [node0, node1], 'theta': None})
for node in self.nodes: for node in self.nodes:
gate_history.append({'gate': 'rx', 'which_qubits': node, 'theta': beta[depth_idx]}) gate_history.append({'gate': 'rx', 'which_qubits': node, 'theta': beta[depth_idx]})
\ No newline at end of file def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _qaoa_layer_display(self, ax, x)
...@@ -26,7 +26,9 @@ from .base import Gate, ParamGate ...@@ -26,7 +26,9 @@ from .base import Gate, ParamGate
from ..backend import Backend from ..backend import Backend
from ..intrinsic import _format_qubits_idx, _get_float_dtype from ..intrinsic import _format_qubits_idx, _get_float_dtype
from typing import Optional, Union, Iterable from typing import Optional, Union, Iterable
from .functional.visual import (_cnot_display, _cswap_display, _tofolli_display, _swap_display,
_cx_like_display, _crx_like_display, _oracle_like_display, _rxx_like_display)
import matplotlib
class CNOT(Gate): class CNOT(Gate):
r"""A collection of CNOT gates. r"""A collection of CNOT gates.
...@@ -54,7 +56,11 @@ class CNOT(Gate): ...@@ -54,7 +56,11 @@ class CNOT(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'cnot' self.gate_info = {
'gatename': 'cnot',
'texname': r'$CNOT$',
'plot_width': 0.2,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -68,6 +74,10 @@ class CNOT(Gate): ...@@ -68,6 +74,10 @@ class CNOT(Gate):
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
state = functional.cnot(state, qubits_idx, self.dtype, self.backend) state = functional.cnot(state, qubits_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _cnot_display(self, ax, x,)
class CX(Gate): class CX(Gate):
...@@ -82,7 +92,11 @@ class CX(Gate): ...@@ -82,7 +92,11 @@ class CX(Gate):
num_qubits: Optional[int] = None, depth: Optional[int] = 1): num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'cnot' self.gate_info = {
'gatename': 'cnot',
'texname': r'$CNOT$',
'plot_width': 0.2,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -96,6 +110,10 @@ class CX(Gate): ...@@ -96,6 +110,10 @@ class CX(Gate):
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
state = functional.cx(state, qubits_idx, self.dtype, self.backend) state = functional.cx(state, qubits_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _cnot_display(self, ax, x,)
class CY(Gate): class CY(Gate):
...@@ -125,7 +143,11 @@ class CY(Gate): ...@@ -125,7 +143,11 @@ class CY(Gate):
num_qubits: Optional[int] = None, depth: Optional[int] = 1): num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'cy' self.gate_info = {
'gatename': 'cy',
'texname': r'$Y$',
'plot_width': 0.4,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -139,6 +161,9 @@ class CY(Gate): ...@@ -139,6 +161,9 @@ class CY(Gate):
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
state = functional.cy(state, qubits_idx, self.dtype, self.backend) state = functional.cy(state, qubits_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _cx_like_display(self, ax, x,)
class CZ(Gate): class CZ(Gate):
...@@ -168,7 +193,11 @@ class CZ(Gate): ...@@ -168,7 +193,11 @@ class CZ(Gate):
num_qubits: Optional[int] = None, depth: Optional[int] = 1): num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'cz' self.gate_info = {
'gatename': 'cz',
'texname': r'$Z$',
'plot_width': 0.4,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -182,6 +211,9 @@ class CZ(Gate): ...@@ -182,6 +211,9 @@ class CZ(Gate):
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
state = functional.cz(state, qubits_idx, self.dtype, self.backend) state = functional.cz(state, qubits_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _cx_like_display(self, ax, x)
class SWAP(Gate): class SWAP(Gate):
...@@ -210,7 +242,11 @@ class SWAP(Gate): ...@@ -210,7 +242,11 @@ class SWAP(Gate):
num_qubits: Optional[int] = None, depth: Optional[int] = 1): num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'swap' self.gate_info = {
'gatename': 'swap',
'texname': r'$SWAP$',
'plot_width': 0.2,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -225,6 +261,9 @@ class SWAP(Gate): ...@@ -225,6 +261,9 @@ class SWAP(Gate):
state = functional.swap(state, qubits_idx, self.dtype, self.backend) state = functional.swap(state, qubits_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _swap_display(self, ax, x,)
class CP(ParamGate): class CP(ParamGate):
r"""A collection of controlled P gates. r"""A collection of controlled P gates.
...@@ -260,7 +299,11 @@ class CP(ParamGate): ...@@ -260,7 +299,11 @@ class CP(ParamGate):
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)] param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'cp' self.gate_info = {
'gatename': 'cp',
'texname': r'$P$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -275,6 +318,9 @@ class CP(ParamGate): ...@@ -275,6 +318,9 @@ class CP(ParamGate):
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _crx_like_display(self, ax, x)
class CRX(ParamGate): class CRX(ParamGate):
r"""A collection of controlled rotation gates about the x-axis. r"""A collection of controlled rotation gates about the x-axis.
...@@ -314,7 +360,11 @@ class CRX(ParamGate): ...@@ -314,7 +360,11 @@ class CRX(ParamGate):
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)] param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'crx' self.gate_info = {
'gatename': 'crx',
'texname': r'$R_{x}$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -345,6 +395,9 @@ class CRX(ParamGate): ...@@ -345,6 +395,9 @@ class CRX(ParamGate):
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _crx_like_display(self, ax, x)
class CRY(ParamGate): class CRY(ParamGate):
r"""A collection of controlled rotation gates about the y-axis. r"""A collection of controlled rotation gates about the y-axis.
...@@ -384,7 +437,11 @@ class CRY(ParamGate): ...@@ -384,7 +437,11 @@ class CRY(ParamGate):
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)] param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'cry' self.gate_info = {
'gatename': 'cry',
'texname': r'$R_{y}$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -414,6 +471,9 @@ class CRY(ParamGate): ...@@ -414,6 +471,9 @@ class CRY(ParamGate):
state = functional.cry( state = functional.cry(
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _crx_like_display(self, ax, x,)
class CRZ(ParamGate): class CRZ(ParamGate):
...@@ -454,7 +514,11 @@ class CRZ(ParamGate): ...@@ -454,7 +514,11 @@ class CRZ(ParamGate):
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)] param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'crz' self.gate_info = {
'gatename': 'crz',
'texname': r'$R_{z}$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -484,6 +548,9 @@ class CRZ(ParamGate): ...@@ -484,6 +548,9 @@ class CRZ(ParamGate):
state = functional.crz( state = functional.crz(
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _crx_like_display(self, ax, x)
class CU(ParamGate): class CU(ParamGate):
...@@ -527,7 +594,11 @@ class CU(ParamGate): ...@@ -527,7 +594,11 @@ class CU(ParamGate):
else: else:
param_shape = [depth, len(self.qubits_idx), 3] param_shape = [depth, len(self.qubits_idx), 3]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'cu' self.gate_info = {
'gatename': 'cu',
'texname': r'$U$',
'plot_width': 1.65,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -557,6 +628,9 @@ class CU(ParamGate): ...@@ -557,6 +628,9 @@ class CU(ParamGate):
state = functional.cu( state = functional.cu(
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _crx_like_display(self, ax, x)
class RXX(ParamGate): class RXX(ParamGate):
...@@ -596,7 +670,11 @@ class RXX(ParamGate): ...@@ -596,7 +670,11 @@ class RXX(ParamGate):
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)] param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'rxx' self.gate_info = {
'gatename': 'rxx',
'texname': r'$R_{xx}$',
'plot_width': 1.0,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -611,6 +689,9 @@ class RXX(ParamGate): ...@@ -611,6 +689,9 @@ class RXX(ParamGate):
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _rxx_like_display(self, ax, x)
class RYY(ParamGate): class RYY(ParamGate):
r"""A collection of RYY gates. r"""A collection of RYY gates.
...@@ -649,7 +730,11 @@ class RYY(ParamGate): ...@@ -649,7 +730,11 @@ class RYY(ParamGate):
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)] param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'ryy' self.gate_info = {
'gatename': 'ryy',
'texname': r'$R_{yy}$',
'plot_width': 1.0,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -663,7 +748,9 @@ class RYY(ParamGate): ...@@ -663,7 +748,9 @@ class RYY(ParamGate):
state = functional.ryy( state = functional.ryy(
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _rxx_like_display(self, ax, x)
class RZZ(ParamGate): class RZZ(ParamGate):
r"""A collection of RZZ gates. r"""A collection of RZZ gates.
...@@ -702,7 +789,11 @@ class RZZ(ParamGate): ...@@ -702,7 +789,11 @@ class RZZ(ParamGate):
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)] param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'rzz' self.gate_info = {
'gatename': 'rzz',
'texname': r'$R_{zz}$',
'plot_width': 1.0,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -717,6 +808,8 @@ class RZZ(ParamGate): ...@@ -717,6 +808,8 @@ class RZZ(ParamGate):
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _rxx_like_display(self, ax, x)
class MS(Gate): class MS(Gate):
r"""A collection of Mølmer-Sørensen (MS) gates for trapped ion devices. r"""A collection of Mølmer-Sørensen (MS) gates for trapped ion devices.
...@@ -743,7 +836,11 @@ class MS(Gate): ...@@ -743,7 +836,11 @@ class MS(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'ms' self.gate_info = {
'gatename': 'ms',
'texname': r'$MS$',
'plot_width': 0.6,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -753,6 +850,8 @@ class MS(Gate): ...@@ -753,6 +850,8 @@ class MS(Gate):
state = functional.ms(state, qubits_idx, self.dtype, self.backend) state = functional.ms(state, qubits_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _oracle_like_display(self, ax, x)
class CSWAP(Gate): class CSWAP(Gate):
r"""A collection of CSWAP (Fredkin) gates. r"""A collection of CSWAP (Fredkin) gates.
...@@ -783,7 +882,11 @@ class CSWAP(Gate): ...@@ -783,7 +882,11 @@ class CSWAP(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=3) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=3)
self.gate_name = 'cswap' self.gate_info = {
'gatename': 'cswap',
'texname': r'$CSWAP$',
'plot_width': 0.2,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -798,6 +901,9 @@ class CSWAP(Gate): ...@@ -798,6 +901,9 @@ class CSWAP(Gate):
state = functional.cswap(state, qubits_idx, self.dtype, self.backend) state = functional.cswap(state, qubits_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _cswap_display(self, ax, x)
class Toffoli(Gate): class Toffoli(Gate):
r"""A collection of Toffoli gates. r"""A collection of Toffoli gates.
...@@ -827,7 +933,11 @@ class Toffoli(Gate): ...@@ -827,7 +933,11 @@ class Toffoli(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=3) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=3)
self.gate_name = 'ccx' self.gate_info = {
'gatename': 'ccx',
'texname': r'$Toffoli$',
'plot_width': 0.2,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -842,6 +952,9 @@ class Toffoli(Gate): ...@@ -842,6 +952,9 @@ class Toffoli(Gate):
state = functional.toffoli(state, qubits_idx, self.dtype, self.backend) state = functional.toffoli(state, qubits_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _tofolli_display(self, ax, x,)
class UniversalTwoQubits(ParamGate): class UniversalTwoQubits(ParamGate):
r"""A collection of universal two-qubit gates. One of such a gate requires 15 parameters. r"""A collection of universal two-qubit gates. One of such a gate requires 15 parameters.
...@@ -869,7 +982,11 @@ class UniversalTwoQubits(ParamGate): ...@@ -869,7 +982,11 @@ class UniversalTwoQubits(ParamGate):
else: else:
param_shape = [depth, len(self.qubits_idx), 15] param_shape = [depth, len(self.qubits_idx), 15]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'uni2' self.gate_info = {
'gatename': 'uni2',
'texname': r'$U$',
'plot_width': 0.6,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -884,6 +1001,9 @@ class UniversalTwoQubits(ParamGate): ...@@ -884,6 +1001,9 @@ class UniversalTwoQubits(ParamGate):
state = functional.universal_two_qubits( state = functional.universal_two_qubits(
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _oracle_like_display(self, ax, x)
class UniversalThreeQubits(ParamGate): class UniversalThreeQubits(ParamGate):
...@@ -912,7 +1032,11 @@ class UniversalThreeQubits(ParamGate): ...@@ -912,7 +1032,11 @@ class UniversalThreeQubits(ParamGate):
else: else:
param_shape = [depth, len(self.qubits_idx), 81] param_shape = [depth, len(self.qubits_idx), 81]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'uni3' self.gate_info = {
'gatename': 'uni3',
'texname': r'$U$',
'plot_width': 0.6,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -927,3 +1051,6 @@ class UniversalThreeQubits(ParamGate): ...@@ -927,3 +1051,6 @@ class UniversalThreeQubits(ParamGate):
state = functional.universal_three_qubits( state = functional.universal_three_qubits(
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
def display_in_circuit(self, ax: matplotlib.axes.Axes, x: float,) -> float:
return _oracle_like_display(self, ax, x)
...@@ -28,7 +28,6 @@ from ..backend import Backend ...@@ -28,7 +28,6 @@ from ..backend import Backend
from paddle_quantum.intrinsic import _format_qubits_idx, _get_float_dtype from paddle_quantum.intrinsic import _format_qubits_idx, _get_float_dtype
from typing import Optional, List, Union, Iterable from typing import Optional, List, Union, Iterable
class H(Gate): class H(Gate):
r"""A collection of single-qubit Hadamard gates. r"""A collection of single-qubit Hadamard gates.
...@@ -50,7 +49,11 @@ class H(Gate): ...@@ -50,7 +49,11 @@ class H(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'h' self.gate_info = {
'gatename': 'h',
'texname': '$H$',
'plot_width': 0.4,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -87,7 +90,11 @@ class S(Gate): ...@@ -87,7 +90,11 @@ class S(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 's' self.gate_info = {
'gatename': 's',
'texname': '$S$',
'plot_width': 0.4,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -102,6 +109,45 @@ class S(Gate): ...@@ -102,6 +109,45 @@ class S(Gate):
state = functional.s(state, qubit_idx, self.dtype, self.backend) state = functional.s(state, qubit_idx, self.dtype, self.backend)
return state return state
class Sdg(Gate):
r"""A collection of single-qubit S dagger (S inverse) gates.
The matrix form of such a gate is:
.. math::
S^\dagger =
\begin{bmatrix}
1&0\\
0&-i
\end{bmatrix}
Args:
qubits_idx: Indices of the qubits on which the gates are applied. Defaults to ``'full'``.
num_qubits: Total number of qubits. Defaults to ``None``.
depth: Number of layers. Defaults to ``1``.
"""
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_info = {
'gatename': 'sdg',
'texname': r'$S^\dagger$',
'plot_width': 0.4,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.Quleaf:
state.gate_history.append({
'gate_name': 'sdg',
'qubits_idx': copy.deepcopy(self.qubits_idx),
'depth': self.depth,
})
return state
for _ in range(0, self.depth):
for qubit_idx in self.qubits_idx:
state = functional.sdg(state, qubit_idx, self.dtype, self.backend)
return state
class T(Gate): class T(Gate):
r"""A collection of single-qubit T gates. r"""A collection of single-qubit T gates.
...@@ -124,7 +170,11 @@ class T(Gate): ...@@ -124,7 +170,11 @@ class T(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 't' self.gate_info = {
'gatename': 't',
'texname': '$T$',
'plot_width': 0.4,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -139,7 +189,44 @@ class T(Gate): ...@@ -139,7 +189,44 @@ class T(Gate):
state = functional.t(state, qubit_idx, self.dtype, self.backend) state = functional.t(state, qubit_idx, self.dtype, self.backend)
return state return state
class Tdg(Gate):
r"""A collection of single-qubit T dagger (T inverse) gates.
The matrix form of such a gate is:
.. math::
T^\dagger =
\begin{bmatrix}
1&0\\
0&e^{-\frac{i\pi}{4}}
\end{bmatrix}
Args:
qubits_idx: Indices of the qubits on which the gates are applied. Defaults to ``'full'``.
num_qubits: Total number of qubits. Defaults to ``None``.
depth: Number of layers. Defaults to ``1``.
"""
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_info = {
'gatename': 'tdg',
'texname': r'$T^\dagger$',
'plot_width': 0.4,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.Quleaf:
state.gate_history.append({
'gate_name': 'tdg',
'qubits_idx': copy.deepcopy(self.qubits_idx),
'depth': self.depth,
})
return state
for _ in range(0, self.depth):
for qubit_idx in self.qubits_idx:
state = functional.tdg(state, qubit_idx, self.dtype, self.backend)
return state
class X(Gate): class X(Gate):
r"""A collection of single-qubit X gates. r"""A collection of single-qubit X gates.
...@@ -160,7 +247,11 @@ class X(Gate): ...@@ -160,7 +247,11 @@ class X(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'x' self.gate_info = {
'gatename': 'x',
'texname': '$X$',
'plot_width': 0.4,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -174,6 +265,7 @@ class X(Gate): ...@@ -174,6 +265,7 @@ class X(Gate):
for qubit_idx in self.qubits_idx: for qubit_idx in self.qubits_idx:
state = functional.x(state, qubit_idx, self.dtype, self.backend) state = functional.x(state, qubit_idx, self.dtype, self.backend)
return state return state
class Y(Gate): class Y(Gate):
...@@ -196,7 +288,11 @@ class Y(Gate): ...@@ -196,7 +288,11 @@ class Y(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'y' self.gate_info = {
'gatename': 'y',
'texname': '$Y$',
'plot_width': 0.4,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -212,6 +308,7 @@ class Y(Gate): ...@@ -212,6 +308,7 @@ class Y(Gate):
return state return state
class Z(Gate): class Z(Gate):
r"""A collection of single-qubit Z gates. r"""A collection of single-qubit Z gates.
...@@ -232,7 +329,11 @@ class Z(Gate): ...@@ -232,7 +329,11 @@ class Z(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'z' self.gate_info = {
'gatename': 'z',
'texname': '$Z$',
'plot_width': 0.4,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -280,7 +381,11 @@ class P(ParamGate): ...@@ -280,7 +381,11 @@ class P(ParamGate):
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)] param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'p' self.gate_info = {
'gatename': 'p',
'texname': '$P$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -296,6 +401,7 @@ class P(ParamGate): ...@@ -296,6 +401,7 @@ class P(ParamGate):
return state return state
class RX(ParamGate): class RX(ParamGate):
r"""A collection of single-qubit rotation gates about the x-axis. r"""A collection of single-qubit rotation gates about the x-axis.
...@@ -328,7 +434,11 @@ class RX(ParamGate): ...@@ -328,7 +434,11 @@ class RX(ParamGate):
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)] param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'rx' self.gate_info = {
'gatename': 'rx',
'texname': r'$R_{x}$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -358,6 +468,7 @@ class RX(ParamGate): ...@@ -358,6 +468,7 @@ class RX(ParamGate):
state = functional.rx( state = functional.rx(
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
class RY(ParamGate): class RY(ParamGate):
...@@ -392,7 +503,11 @@ class RY(ParamGate): ...@@ -392,7 +503,11 @@ class RY(ParamGate):
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)] param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'ry' self.gate_info = {
'gatename': 'ry',
'texname': r'$R_{y}$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -422,6 +537,8 @@ class RY(ParamGate): ...@@ -422,6 +537,8 @@ class RY(ParamGate):
state = functional.ry( state = functional.ry(
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
class RZ(ParamGate): class RZ(ParamGate):
...@@ -456,7 +573,11 @@ class RZ(ParamGate): ...@@ -456,7 +573,11 @@ class RZ(ParamGate):
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)] param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'rz' self.gate_info = {
'gatename': 'rz',
'texname': r'$R_{z}$',
'plot_width': 0.9,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -486,6 +607,7 @@ class RZ(ParamGate): ...@@ -486,6 +607,7 @@ class RZ(ParamGate):
state = functional.rz( state = functional.rz(
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
class U3(ParamGate): class U3(ParamGate):
...@@ -526,7 +648,11 @@ class U3(ParamGate): ...@@ -526,7 +648,11 @@ class U3(ParamGate):
else: else:
param_shape = [depth, len(self.qubits_idx), 3] param_shape = [depth, len(self.qubits_idx), 3]
self.theta_generation(param, param_shape) self.theta_generation(param, param_shape)
self.gate_name = 'u' self.gate_info = {
'gatename': 'u',
'texname': r'$U$',
'plot_width': 1.65,
}
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -556,3 +682,5 @@ class U3(ParamGate): ...@@ -556,3 +682,5 @@ class U3(ParamGate):
state = functional.u3( state = functional.u3(
state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend) state, self.theta[depth_idx, param_idx], qubit_idx, self.dtype, self.backend)
return state return state
\ No newline at end of file
...@@ -58,7 +58,6 @@ class Hamiltonian: ...@@ -58,7 +58,6 @@ class Hamiltonian:
self.__compress() self.__compress()
def __getitem__(self, indices): def __getitem__(self, indices):
new_pauli_str = []
if isinstance(indices, int): if isinstance(indices, int):
indices = [indices] indices = [indices]
elif isinstance(indices, slice): elif isinstance(indices, slice):
...@@ -66,8 +65,8 @@ class Hamiltonian: ...@@ -66,8 +65,8 @@ class Hamiltonian:
elif isinstance(indices, tuple): elif isinstance(indices, tuple):
indices = list(indices) indices = list(indices)
for index in indices: new_pauli_str = [[self.coefficients[index], ','.join(self.terms[index])] for index in indices]
new_pauli_str.append([self.coefficients[index], ','.join(self.terms[index])])
return Hamiltonian(new_pauli_str, compress=False) return Hamiltonian(new_pauli_str, compress=False)
def __add__(self, h_2): def __add__(self, h_2):
...@@ -90,7 +89,7 @@ class Hamiltonian: ...@@ -90,7 +89,7 @@ class Hamiltonian:
def __str__(self): def __str__(self):
str_out = '' str_out = ''
for idx in range(self.n_terms): for idx in range(self.n_terms):
str_out += '{} '.format(self.coefficients[idx]) str_out += f'{self.coefficients[idx]} '
for _ in range(len(self.terms[idx])): for _ in range(len(self.terms[idx])):
str_out += self.terms[idx][_] str_out += self.terms[idx][_]
if _ != len(self.terms[idx]) - 1: if _ != len(self.terms[idx]) - 1:
...@@ -120,9 +119,7 @@ class Hamiltonian: ...@@ -120,9 +119,7 @@ class Hamiltonian:
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
return self.__terms return self.__terms
else:
return self.__terms
@property @property
def coefficients(self) -> list: def coefficients(self) -> list:
...@@ -130,9 +127,7 @@ class Hamiltonian: ...@@ -130,9 +127,7 @@ class Hamiltonian:
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
return self.__coefficients return self.__coefficients
else:
return self.__coefficients
@property @property
def pauli_words(self) -> list: def pauli_words(self) -> list:
...@@ -140,9 +135,7 @@ class Hamiltonian: ...@@ -140,9 +135,7 @@ class Hamiltonian:
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
return self.__pauli_words return self.__pauli_words
else:
return self.__pauli_words
@property @property
def pauli_words_r(self) -> list: def pauli_words_r(self) -> list:
...@@ -150,9 +143,7 @@ class Hamiltonian: ...@@ -150,9 +143,7 @@ class Hamiltonian:
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
return self.__pauli_words_r return self.__pauli_words_r
else:
return self.__pauli_words_r
@property @property
def pauli_words_matrix(self) -> list: def pauli_words_matrix(self) -> list:
...@@ -183,7 +174,7 @@ class Hamiltonian: ...@@ -183,7 +174,7 @@ class Hamiltonian:
paddle.to_tensor([[1, 0], [0, 1]], dtype=paddle_quantum.get_dtype()) paddle.to_tensor([[1, 0], [0, 1]], dtype=paddle_quantum.get_dtype())
) )
else: else:
raise ValueError('wrong format of string ' + string) raise ValueError(f'wrong format of string {string}')
return matrix return matrix
return list(map(to_matrix, copy.deepcopy(self.pauli_words_r))) return list(map(to_matrix, copy.deepcopy(self.pauli_words_r)))
...@@ -194,9 +185,7 @@ class Hamiltonian: ...@@ -194,9 +185,7 @@ class Hamiltonian:
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
return self.__sites return self.__sites
else:
return self.__sites
@property @property
def n_qubits(self) -> int: def n_qubits(self) -> int:
...@@ -204,9 +193,7 @@ class Hamiltonian: ...@@ -204,9 +193,7 @@ class Hamiltonian:
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
return self.__nqubits return self.__nqubits
else:
return self.__nqubits
def __decompose(self): def __decompose(self):
r"""decompose the Hamiltonian into vairious forms r"""decompose the Hamiltonian into vairious forms
...@@ -235,20 +222,20 @@ class Hamiltonian: ...@@ -235,20 +222,20 @@ class Hamiltonian:
match_i = re.match(r'I', single_pauli_term, flags=re.I) match_i = re.match(r'I', single_pauli_term, flags=re.I)
if match_i: if match_i:
assert single_pauli_term[0].upper() == 'I', \ assert single_pauli_term[0].upper() == 'I', \
'The offset is defined with a sole letter "I", i.e. (3.0, "I")' 'The offset is defined with a sole letter "I", i.e. (3.0, "I")'
pauli_word_r += 'I' pauli_word_r += 'I'
site.append('') site.append('')
else: else:
match = re.match(r'([XYZ])([0-9]+)', single_pauli_term, flags=re.I) match = re.match(r'([XYZ])([0-9]+)', single_pauli_term, flags=re.I)
if match: if not match:
pauli_word_r += match.group(1).upper()
assert int(match.group(2)) not in site, 'each Pauli operator should act on different qubit'
site.append(int(match.group(2)))
else:
raise Exception( raise Exception(
'Operators should be defined with a string composed of Pauli operators followed' + 'Operators should be defined with a string composed of Pauli operators followed' +
'by qubit index on which it act, separated with ",". i.e. "Z0, X1"') 'by qubit index on which it act, separated with ",". i.e. "Z0, X1"')
self.__nqubits = max(self.__nqubits, int(match.group(2)) + 1) pauli_word_r += match[1].upper()
assert int(match[2]) not in site, 'each Pauli operator should act on different qubit'
site.append(int(match[2]))
self.__nqubits = max(self.__nqubits, int(match[2]) + 1)
self.__pauli_words_r.append(pauli_word_r) self.__pauli_words_r.append(pauli_word_r)
self.__sites.append(site) self.__sites.append(site)
new_pauli_str.append([float(coefficient), pauli_term.upper()]) new_pauli_str.append([float(coefficient), pauli_term.upper()])
...@@ -271,19 +258,14 @@ class Hamiltonian: ...@@ -271,19 +258,14 @@ class Hamiltonian:
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
else:
pass
new_pauli_str = [] new_pauli_str = []
flag_merged = [False for _ in range(self.n_terms)] flag_merged = [False for _ in range(self.n_terms)]
for term_idx_1 in range(self.n_terms): for term_idx_1 in range(self.n_terms):
if not flag_merged[term_idx_1]: if not flag_merged[term_idx_1]:
for term_idx_2 in range(term_idx_1 + 1, self.n_terms): for term_idx_2 in range(term_idx_1 + 1, self.n_terms):
if not flag_merged[term_idx_2]: if not flag_merged[term_idx_2] and self.pauli_words[term_idx_1] == self.pauli_words[term_idx_2]:
if self.pauli_words[term_idx_1] == self.pauli_words[term_idx_2]: self.__coefficients[term_idx_1] += self.__coefficients[term_idx_2]
self.__coefficients[term_idx_1] += self.__coefficients[term_idx_2] flag_merged[term_idx_2] = True
flag_merged[term_idx_2] = True
else:
pass
if self.__coefficients[term_idx_1] != 0: if self.__coefficients[term_idx_1] != 0:
new_pauli_str.append([self.__coefficients[term_idx_1], ','.join(self.__terms[term_idx_1])]) new_pauli_str.append([self.__coefficients[term_idx_1], ','.join(self.__terms[term_idx_1])])
self.__pauli_str = new_pauli_str self.__pauli_str = new_pauli_str
...@@ -312,8 +294,6 @@ class Hamiltonian: ...@@ -312,8 +294,6 @@ class Hamiltonian:
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
else:
pass
return self.coefficients, self.__pauli_words return self.coefficients, self.__pauli_words
def construct_h_matrix(self, qubit_num: Optional[int] = None) -> np.ndarray: def construct_h_matrix(self, qubit_num: Optional[int] = None) -> np.ndarray:
...@@ -332,7 +312,7 @@ class Hamiltonian: ...@@ -332,7 +312,7 @@ class Hamiltonian:
if type(site[0]) is int: if type(site[0]) is int:
qubit_num = max(qubit_num, max(site) + 1) qubit_num = max(qubit_num, max(site) + 1)
else: else:
assert qubit_num >= self.n_qubits, "输入的量子数不小于哈密顿量表达式中所对应的量子比特数" assert qubit_num >= self.n_qubits, "the input number of qubits must be no less than the number of qubits of this Hamiltonian"
n_qubit = qubit_num n_qubit = qubit_num
h_matrix = np.zeros([2 ** n_qubit, 2 ** n_qubit], dtype='complex64') h_matrix = np.zeros([2 ** n_qubit, 2 ** n_qubit], dtype='complex64')
spin_ops = SpinOps(n_qubit, use_sparse=True) spin_ops = SpinOps(n_qubit, use_sparse=True)
......
...@@ -21,12 +21,16 @@ import numpy as np ...@@ -21,12 +21,16 @@ import numpy as np
import paddle import paddle
from typing import Union, Iterable, List from typing import Union, Iterable, List
from .base import get_dtype
def _zero(dtype):
def _zero(dtype = None):
dtype = get_dtype() if dtype is None else dtype
return paddle.to_tensor(0, dtype=dtype) return paddle.to_tensor(0, dtype=dtype)
def _one(dtype): def _one(dtype = None):
dtype = get_dtype() if dtype is None else dtype
return paddle.to_tensor(1, dtype=dtype) return paddle.to_tensor(1, dtype=dtype)
...@@ -65,6 +69,7 @@ def _format_qubits_idx( ...@@ -65,6 +69,7 @@ def _format_qubits_idx(
raise TypeError( raise TypeError(
"The qubits_idx should be iterable such as list, tuple, and so on whose elements are all integers." "The qubits_idx should be iterable such as list, tuple, and so on whose elements are all integers."
"And the length of acted_qubits should be consistent with the corresponding gate." "And the length of acted_qubits should be consistent with the corresponding gate."
f"\n Received qubits_idx type {type(qubits_idx)}, qubits # {len(qubits_idx)}, gate dimension {num_acted_qubits}"
) )
return qubits_idx return qubits_idx
......
...@@ -14,22 +14,23 @@ ...@@ -14,22 +14,23 @@
# limitations under the License. # limitations under the License.
r""" r"""
The common linear algorithm in paddle quantum. The library of functions in linear algebra.
""" """
from typing import Optional, Union
import paddle import paddle
import math import math
import numpy as np import numpy as np
import scipy import scipy
from scipy.stats import unitary_group from scipy.stats import unitary_group
from functools import reduce from functools import reduce
from typing import Optional, Union, Callable
import paddle_quantum import paddle_quantum as pq
from paddle_quantum.intrinsic import _get_float_dtype from .intrinsic import _get_float_dtype
from .state import State, _type_fetch, _type_transform
def abs_norm(mat: paddle.Tensor) -> float: def abs_norm(mat: Union[np.ndarray, paddle.Tensor, State]) -> float:
r""" tool for calculation of matrix norm r""" tool for calculation of matrix norm
Args: Args:
...@@ -39,11 +40,12 @@ def abs_norm(mat: paddle.Tensor) -> float: ...@@ -39,11 +40,12 @@ def abs_norm(mat: paddle.Tensor) -> float:
norm of mat norm of mat
""" """
mat = mat.cast(paddle_quantum.get_dtype()) mat = _type_transform(mat, "tensor")
mat = mat.cast(pq.get_dtype())
return paddle.norm(paddle.abs(mat)).item() return paddle.norm(paddle.abs(mat)).item()
def dagger(mat: paddle.Tensor) -> paddle.Tensor: def dagger(mat: Union[np.ndarray, paddle.Tensor]) -> Union[np.ndarray, paddle.Tensor]:
r""" tool for calculation of matrix dagger r""" tool for calculation of matrix dagger
Args: Args:
...@@ -53,20 +55,22 @@ def dagger(mat: paddle.Tensor) -> paddle.Tensor: ...@@ -53,20 +55,22 @@ def dagger(mat: paddle.Tensor) -> paddle.Tensor:
The dagger of matrix The dagger of matrix
""" """
return paddle.conj(mat.T) type_str = _type_fetch(mat)
return np.conj(mat.T) if type_str == "numpy" else paddle.conj(mat.T)
def is_hermitian(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool: def is_hermitian(mat: Union[np.ndarray, paddle.Tensor], eps: Optional[float] = 1e-6) -> bool:
r""" verify whether mat ``P`` is Hermitian r""" verify whether ``mat`` is Hermitian
Args: Args:
mat: hermitian candidate mat: hermitian candidate :math:`P`
eps: tolerance of error eps: tolerance of error
Returns: Returns:
determine whether :math:`mat - mat^\dagger = 0` determine whether :math:`P - P^\dagger = 0`
""" """
mat = _type_transform(mat, "tensor")
shape = mat.shape shape = mat.shape
if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])): if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])):
# not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits # not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits
...@@ -74,17 +78,18 @@ def is_hermitian(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool: ...@@ -74,17 +78,18 @@ def is_hermitian(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool:
return abs_norm(mat - dagger(mat)) < eps return abs_norm(mat - dagger(mat)) < eps
def is_projector(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool: def is_projector(mat: Union[np.ndarray, paddle.Tensor], eps: Optional[float] = 1e-6) -> bool:
r""" verify whether mat ``P`` is a projector r""" verify whether ``mat`` is a projector
Args: Args:
mat: projector candidate mat: projector candidate :math:`P`
eps: tolerance of error eps: tolerance of error
Returns: Returns:
determine whether :math:`PP - P = 0` determine whether :math:`PP - P = 0`
""" """
mat = _type_transform(mat, "tensor")
shape = mat.shape shape = mat.shape
if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])): if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])):
# not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits # not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits
...@@ -92,18 +97,20 @@ def is_projector(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool: ...@@ -92,18 +97,20 @@ def is_projector(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool:
return abs_norm(mat @ mat - mat) < eps return abs_norm(mat @ mat - mat) < eps
def is_unitary(mat: paddle.Tensor, eps: Optional[float] = 1e-5) -> bool: def is_unitary(mat: Union[np.ndarray, paddle.Tensor], eps: Optional[float] = 1e-4) -> bool:
r""" verify whether mat ``P`` is a unitary r""" verify whether ``mat`` is a unitary
Args: Args:
mat: unitary candidate mat: unitary candidate :math:`P`
eps: tolerance of error eps: tolerance of error
Returns: Returns:
determine whether :math:`PP^\dagger - I = 0` determine whether :math:`PP^\dagger - I = 0`
""" """
mat = _type_transform(mat, "tensor").cast('complex128')
shape = mat.shape shape = mat.shape
eps = min(eps * shape[0], 1e-2)
if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])): if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])):
# not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits # not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits
return False return False
...@@ -111,36 +118,41 @@ def is_unitary(mat: paddle.Tensor, eps: Optional[float] = 1e-5) -> bool: ...@@ -111,36 +118,41 @@ def is_unitary(mat: paddle.Tensor, eps: Optional[float] = 1e-5) -> bool:
def hermitian_random(num_qubits: int) -> paddle.Tensor: def hermitian_random(num_qubits: int) -> paddle.Tensor:
r"""randomly generate a :math:`2^num_qubits \times 2^num_qubits` hermitian matrix r"""randomly generate a :math:`2^n \times 2^n` hermitian matrix
Args: Args:
num_qubits: log2(dimension) num_qubits: number of qubits :math:`n`
Returns: Returns:
a :math:`2^num_qubits \times 2^num_qubits` hermitian matrix a :math:`2^n \times 2^n` hermitian matrix
""" """
assert num_qubits > 0 assert num_qubits > 0
n = 2 ** num_qubits n = 2 ** num_qubits
float_dtype = _get_float_dtype(paddle_quantum.get_dtype()) mat = np.random.randn(n, n) + 1j * np.random.randn(n, n)
vec = paddle.randn([n, n], dtype=float_dtype) + 1j * paddle.randn([n, n], dtype=float_dtype) for i in range(n):
mat = vec @ dagger(vec) mat[i, i] = np.abs(mat[i, i])
return mat / paddle.trace(mat) 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))
return paddle.to_tensor(mat / max_eigval, dtype=pq.get_dtype())
def orthogonal_projection_random(num_qubits: int) -> paddle.Tensor: def orthogonal_projection_random(num_qubits: int) -> paddle.Tensor:
r"""randomly generate a :math:`2^num_qubits \times 2^num_qubits` rank-1 orthogonal projector r"""randomly generate a :math:`2^n \times 2^n` rank-1 orthogonal projector
Args: Args:
num_qubits: log2(dimension) num_qubits: number of qubits :math:`n`
Returns: Returns:
a 2^num_qubits x 2^num_qubits orthogonal projector a :math:`2^n \times 2^n` orthogonal projector
""" """
assert num_qubits > 0 assert num_qubits > 0
n = 2 ** num_qubits n = 2 ** num_qubits
float_dtype = _get_float_dtype(paddle_quantum.get_dtype()) float_dtype = _get_float_dtype(pq.get_dtype())
vec = paddle.randn([n, 1], dtype=float_dtype) + 1j * paddle.randn([n, 1], dtype=float_dtype) vec = paddle.randn([n, 1], dtype=float_dtype) + 1j * paddle.randn([n, 1], dtype=float_dtype)
mat = vec @ dagger(vec) mat = vec @ dagger(vec)
return mat / paddle.trace(mat) return mat / paddle.trace(mat)
...@@ -150,42 +162,41 @@ def density_matrix_random(num_qubits: int) -> paddle.Tensor: ...@@ -150,42 +162,41 @@ def density_matrix_random(num_qubits: int) -> paddle.Tensor:
r""" randomly generate an num_qubits-qubit state in density matrix form r""" randomly generate an num_qubits-qubit state in density matrix form
Args: Args:
num_qubits: number of qubits num_qubits: number of qubits :math:`n`
Returns: Returns:
a 2^num_qubits x 2^num_qubits density matrix a :math:`2^n \times 2^n` density matrix
""" """
float_dtype = _get_float_dtype(paddle_quantum.get_dtype()) float_dtype = _get_float_dtype(pq.get_dtype())
real = paddle.rand([2 ** num_qubits, 2 ** num_qubits], dtype=float_dtype) real = paddle.rand([2 ** num_qubits, 2 ** num_qubits], dtype=float_dtype)
imag = paddle.rand([2 ** num_qubits, 2 ** num_qubits], dtype=float_dtype) imag = paddle.rand([2 ** num_qubits, 2 ** num_qubits], dtype=float_dtype)
M = real + 1j * imag M = real + 1j * imag
M = M @ dagger(M) M = M @ dagger(M)
rho = M / paddle.trace(M) return M / paddle.trace(M)
return rho
def unitary_random(num_qubits: int) -> paddle.Tensor: def unitary_random(num_qubits: int) -> paddle.Tensor:
r"""randomly generate a :math:`2^num_qubits \times 2^num_qubits` unitary r"""randomly generate a :math:`2^n \times 2^n` unitary
Args: Args:
num_qubits: :math:`\log_{2}(dimension)` num_qubits: number of qubits :math:`n`
Returns: Returns:
a :math:`2^num_qubits \times 2^num_qubits` unitary matrix a :math:`2^n \times 2^n` unitary matrix
""" """
return paddle.to_tensor(unitary_group.rvs(2 ** num_qubits), dtype=paddle_quantum.get_dtype()) return paddle.to_tensor(unitary_group.rvs(2 ** num_qubits), dtype=pq.get_dtype())
def unitary_hermitian_random(num_qubits: int) -> paddle.Tensor: def unitary_hermitian_random(num_qubits: int) -> paddle.Tensor:
r"""randomly generate a :math:`2^num_qubits \times 2^num_qubits` hermitian unitary r"""randomly generate a :math:`2^n \times 2^n` hermitian unitary
Args: Args:
num_qubits: :math:`\log_{2}(dimension)` num_qubits: number of qubits :math:`n`
Returns: Returns:
a :math:`2^num_qubits \times 2^num_qubits` hermitian unitary matrix a :math:`2^n \times 2^n` hermitian unitary matrix
""" """
proj_mat = orthogonal_projection_random(num_qubits) proj_mat = orthogonal_projection_random(num_qubits)
...@@ -194,14 +205,14 @@ def unitary_hermitian_random(num_qubits: int) -> paddle.Tensor: ...@@ -194,14 +205,14 @@ def unitary_hermitian_random(num_qubits: int) -> paddle.Tensor:
def unitary_random_with_hermitian_block(num_qubits: int, is_unitary: bool = False) -> paddle.Tensor: def unitary_random_with_hermitian_block(num_qubits: int, is_unitary: bool = False) -> paddle.Tensor:
r"""randomly generate a unitary :math:`2^num_qubits \times 2^num_qubits` matrix that is a block encoding of a :math:`2^{num_qubits/2} \times 2^{num_qubits/2}` Hermitian matrix 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
Args: Args:
num_qubits: :math:`\log_{2}(dimension)` num_qubits: number of qubits :math:`n`
is_unitary: whether the hermitian block is a unitary divided by 2 (for tutorial only) is_unitary: whether the hermitian block is a unitary divided by 2 (for tutorial only)
Returns: Returns:
a :math:`2^num_qubits \times 2^num_qubits` unitary matrix that its upper-left block is a Hermitian matrix a :math:`2^n \times 2^n` unitary matrix that its upper-left block is a Hermitian matrix
""" """
assert num_qubits > 0 assert num_qubits > 0
...@@ -215,17 +226,47 @@ def unitary_random_with_hermitian_block(num_qubits: int, is_unitary: bool = Fals ...@@ -215,17 +226,47 @@ def unitary_random_with_hermitian_block(num_qubits: int, is_unitary: bool = Fals
mat = np.block([[mat0, mat1], [mat1, mat0]]) mat = np.block([[mat0, mat1], [mat1, mat0]])
return paddle.to_tensor(mat, dtype=paddle_quantum.get_dtype()) return paddle.to_tensor(mat, dtype=pq.get_dtype())
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)
def haar_orthogonal(num_qubits: int) -> paddle.Tensor: def haar_orthogonal(num_qubits: int) -> paddle.Tensor:
r""" randomly generate an orthogonal matrix following Haar random, referenced by arXiv:math-ph/0609050v2 r""" randomly generate an orthogonal matrix following Haar random, referenced by arXiv:math-ph/0609050v2
Args: Args:
num_qubits: number of qubits num_qubits: number of qubits :math:`n`
Returns: Returns:
a :math:`2^num_qubits \times 2^num_qubits` orthogonal matrix a :math:`2^n \times 2^n` orthogonal matrix
""" """
# Matrix dimension # Matrix dimension
...@@ -237,17 +278,17 @@ def haar_orthogonal(num_qubits: int) -> paddle.Tensor: ...@@ -237,17 +278,17 @@ def haar_orthogonal(num_qubits: int) -> paddle.Tensor:
# Step 3: make the decomposition unique # Step 3: make the decomposition unique
mat_lambda = np.diag(mat_r) / abs(np.diag(mat_r)) mat_lambda = np.diag(mat_r) / abs(np.diag(mat_r))
mat_u = mat_q @ np.diag(mat_lambda) mat_u = mat_q @ np.diag(mat_lambda)
return paddle.to_tensor(mat_u, dtype=paddle_quantum.get_dtype()) return paddle.to_tensor(mat_u, dtype=pq.get_dtype())
def haar_unitary(num_qubits: int) -> paddle.Tensor: def haar_unitary(num_qubits: int) -> paddle.Tensor:
r""" randomly generate a unitary following Haar random, referenced by arXiv:math-ph/0609050v2 r""" randomly generate a unitary following Haar random, referenced by arXiv:math-ph/0609050v2
Args: Args:
num_qubits: number of qubits num_qubits: number of qubits :math:`n`
Returns: Returns:
a :math:`2^num_qubits \times 2^num_qubits` unitary a :math:`2^n \times 2^n` unitary
""" """
# Matrix dimension # Matrix dimension
...@@ -259,18 +300,18 @@ def haar_unitary(num_qubits: int) -> paddle.Tensor: ...@@ -259,18 +300,18 @@ def haar_unitary(num_qubits: int) -> paddle.Tensor:
# Step 3: make the decomposition unique # Step 3: make the decomposition unique
mat_lambda = np.diag(mat_r) / np.abs(np.diag(mat_r)) mat_lambda = np.diag(mat_r) / np.abs(np.diag(mat_r))
mat_u = mat_q @ np.diag(mat_lambda) mat_u = mat_q @ np.diag(mat_lambda)
return paddle.to_tensor(mat_u, dtype=paddle_quantum.get_dtype()) return paddle.to_tensor(mat_u, dtype=pq.get_dtype())
def haar_state_vector(num_qubits: int, is_real: Optional[bool] = False) -> paddle.Tensor: def haar_state_vector(num_qubits: int, is_real: Optional[bool] = False) -> paddle.Tensor:
r""" randomly generate a state vector following Haar random r""" randomly generate a state vector following Haar random
Args: Args:
num_qubits: number of qubits num_qubits: number of qubits :math:`n`
is_real: whether the vector is real, default to be False is_real: whether the vector is real, default to be False
Returns: Returns:
a :math:`2^num_qubits \times 1` state vector a :math:`2^n \times 1` state vector
""" """
# Vector dimension # Vector dimension
...@@ -286,19 +327,19 @@ def haar_state_vector(num_qubits: int, is_real: Optional[bool] = False) -> paddl ...@@ -286,19 +327,19 @@ def haar_state_vector(num_qubits: int, is_real: Optional[bool] = False) -> paddl
# Perform u onto |0>, i.e., the first column of u # Perform u onto |0>, i.e., the first column of u
phi = unitary[:, 0] phi = unitary[:, 0]
return paddle.to_tensor(phi, dtype=paddle_quantum.get_dtype()) return paddle.to_tensor(phi, dtype=pq.get_dtype())
def haar_density_operator(num_qubits: int, rank: Optional[int] = None, is_real: Optional[bool] = False) -> paddle.Tensor: 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 r""" randomly generate a density matrix following Haar random
Args: Args:
num_qubits: number of qubits num_qubits: number of qubits :math:`n`
rank: rank of density matrix, default to be False refering to full ranks rank: rank of density matrix, default to be False refering to full ranks
is_real: whether the density matrix is real, default to be False is_real: whether the density matrix is real, default to be False
Returns: Returns:
a :math:`2^num_qubits \times 2^num_qubits` density matrix a :math:`2^n \times 2^n` density matrix
""" """
dim = 2 ** num_qubits dim = 2 ** num_qubits
rank = rank if rank is not None else dim rank = rank if rank is not None else dim
...@@ -310,14 +351,38 @@ def haar_density_operator(num_qubits: int, rank: Optional[int] = None, is_real: ...@@ -310,14 +351,38 @@ def haar_density_operator(num_qubits: int, rank: Optional[int] = None, is_real:
ginibre_matrix = np.random.randn(dim, rank) + 1j * np.random.randn(dim, rank) ginibre_matrix = np.random.randn(dim, rank) + 1j * np.random.randn(dim, rank)
rho = ginibre_matrix @ ginibre_matrix.conj().T rho = ginibre_matrix @ ginibre_matrix.conj().T
rho = rho / np.trace(rho) rho = rho / np.trace(rho)
return paddle.to_tensor(rho / np.trace(rho), dtype=paddle_quantum.get_dtype()) return paddle.to_tensor(rho / np.trace(rho), dtype=pq.get_dtype())
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)
def NKron( def NKron(
matrix_A: Union[paddle.Tensor, np.ndarray], matrix_A: Union[paddle.Tensor, np.ndarray],
matrix_B: Union[paddle.Tensor, np.ndarray], matrix_B: Union[paddle.Tensor, np.ndarray],
*args: Union[paddle.Tensor, np.ndarray] *args: Union[paddle.Tensor, np.ndarray]
) -> Union[paddle.Tensor, np.ndarray]: ) -> Union[paddle.Tensor, np.ndarray]:
r""" calculate Kronecker product of at least two matrices r""" calculate Kronecker product of at least two matrices
Args: Args:
...@@ -330,20 +395,52 @@ def NKron( ...@@ -330,20 +395,52 @@ def NKron(
.. code-block:: python .. code-block:: python
from paddle_quantum.state import density_op_random from pq.state import density_op_random
from paddle_quantum.utils import NKron from pq.linalg import NKron
A = density_op_random(2) A = density_op_random(2)
B = density_op_random(2) B = density_op_random(2)
C = density_op_random(2) C = density_op_random(2)
result = NKron(A, B, C) result = NKron(A, B, C)
Note: Note:
result should be A \otimes B \otimes C ``result`` from above code block should be A \otimes B \otimes C
""" """
is_ndarray = False type_A, type_B = _type_fetch(matrix_A), _type_fetch(matrix_A)
if isinstance(matrix_A, np.ndarray): assert type_A == type_B, f"the input data types do not agree: received {type_A} and {type_B}"
is_ndarray = True
if not is_ndarray: if type_A == "tensor":
return reduce(lambda result, index: paddle.kron(result, index), args, paddle.kron(matrix_A, matrix_B), ) return reduce(lambda result, index: paddle.kron(result, index), args, paddle.kron(matrix_A, matrix_B), )
else: else:
return reduce(lambda result, index: np.kron(result, index), args, np.kron(matrix_A, matrix_B), ) return reduce(lambda result, index: np.kron(result, index), args, np.kron(matrix_A, matrix_B), )
def herm_transform(fcn: Callable[[float], float], mat: Union[paddle.Tensor, np.ndarray, State],
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
...@@ -18,7 +18,6 @@ The source file of the LoccAnsatz class. ...@@ -18,7 +18,6 @@ The source file of the LoccAnsatz class.
""" """
import collections import collections
from matplotlib import docstring
import paddle import paddle
import paddle_quantum import paddle_quantum
from ..gate import H, S, T, X, Y, Z, P, RX, RY, RZ, U3 from ..gate import H, S, T, X, Y, Z, P, RX, RY, RZ, U3
......
...@@ -19,15 +19,16 @@ The source file of the class for the special quantum operator. ...@@ -19,15 +19,16 @@ The source file of the class for the special quantum operator.
import numpy as np import numpy as np
import paddle import paddle
import paddle_quantum
import warnings import warnings
import paddle_quantum as pq
from ..base import Operator from ..base import Operator
from typing import Union, Iterable
from ..intrinsic import _format_qubits_idx, _get_float_dtype
from ..backend import Backend from ..backend import Backend
from ..backend import state_vector, density_matrix, unitary_matrix from ..backend import state_vector, density_matrix, unitary_matrix
from ..linalg import abs_norm from ..intrinsic import _format_qubits_idx, _get_float_dtype
from ..state import State
from ..qinfo import partial_trace_discontiguous from ..qinfo import partial_trace_discontiguous
from typing import Union, Iterable
class ResetState(Operator): class ResetState(Operator):
...@@ -81,19 +82,19 @@ class Collapse(Operator): ...@@ -81,19 +82,19 @@ class Collapse(Operator):
measure_basis: Union[Iterable[paddle.Tensor], str] = 'z'): measure_basis: Union[Iterable[paddle.Tensor], str] = 'z'):
super().__init__() super().__init__()
self.measure_basis = [] self.measure_basis = []
# the qubit indices must be sorted # the qubit indices must be sorted
self.qubits_idx = sorted(_format_qubits_idx(qubits_idx, num_qubits)) self.qubits_idx = sorted(_format_qubits_idx(qubits_idx, num_qubits))
self.desired_result = desired_result self.desired_result = desired_result
self.if_print = if_print self.if_print = if_print
if measure_basis == 'z' or measure_basis == 'computational_basis': if measure_basis in ['z', 'computational_basis']:
self.measure_basis = 'z' self.measure_basis = 'z'
else: else:
raise NotImplementedError raise NotImplementedError
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: State) -> State:
r"""Compute the collapse of the input state. r"""Compute the collapse of the input state.
Args: Args:
...@@ -102,70 +103,71 @@ class Collapse(Operator): ...@@ -102,70 +103,71 @@ class Collapse(Operator):
Returns: Returns:
The collapsed quantum state. The collapsed quantum state.
""" """
complex_dtype = paddle_quantum.get_dtype() complex_dtype = pq.get_dtype()
float_dtype = _get_float_dtype(complex_dtype) float_dtype = _get_float_dtype(complex_dtype)
num_qubits = state.num_qubits
backend = state.backend backend = state.backend
num_acted_qubits = len(self.qubits_idx) num_acted_qubits = len(self.qubits_idx)
desired_result = self.desired_result desired_result = self.desired_result
desired_result = int(desired_result, 2) if isinstance(desired_result, str) else desired_result desired_result = int(desired_result, 2) if isinstance(desired_result, str) else desired_result
def projector_gen() -> paddle.Tensor:
proj = paddle.zeros([2 ** num_acted_qubits, 2 ** num_acted_qubits])
proj[desired_result, desired_result] += 1
proj = proj.cast(complex_dtype)
return proj
num_qubits = state.num_qubits
# when backend is unitary # when backend is unitary
if backend == Backend.UnitaryMatrix: if backend == Backend.UnitaryMatrix:
assert isinstance(desired_result, int), "desired_result cannot be None in unitary_matrix backend" assert isinstance(desired_result, int), "desired_result cannot be None in unitary_matrix backend"
warnings.warn( warnings.warn(
"the unitary_matrix of a circuit containing Collapse operator is no longer a unitary" "the unitary_matrix of a circuit containing Collapse operator is no longer a unitary"
) )
# determine local projector local_projector = projector_gen()
local_projector = paddle.zeros([2 ** num_acted_qubits, 2 ** num_acted_qubits])
local_projector[desired_result, desired_result] += 1
local_projector = local_projector.cast(complex_dtype)
projected_state = unitary_matrix.unitary_transformation(state.data, local_projector, self.qubits_idx, num_qubits) projected_state = unitary_matrix.unitary_transformation(state.data, local_projector, self.qubits_idx, num_qubits)
return paddle_quantum.State(projected_state, backend=Backend.UnitaryMatrix) return State(projected_state, backend=Backend.UnitaryMatrix)
# retrieve prob_amplitude # retrieve prob_amplitude
if backend == Backend.StateVector: rho = state.ket @ state.bra if backend == Backend.StateVector else state.data
rho = state.ket @ state.bra
else:
rho = state.data
rho = partial_trace_discontiguous(rho, self.qubits_idx) rho = partial_trace_discontiguous(rho, self.qubits_idx)
prob_amplitude = paddle.zeros([2 ** num_acted_qubits], dtype=float_dtype) prob_amplitude = paddle.zeros([2 ** num_acted_qubits], dtype=float_dtype)
for idx in range(0, 2 ** num_acted_qubits): for idx in range(2 ** num_acted_qubits):
prob_amplitude[idx] += rho[idx, idx].real() prob_amplitude[idx] += rho[idx, idx].real()
prob_amplitude /= paddle.sum(prob_amplitude) prob_amplitude /= paddle.sum(prob_amplitude)
if desired_result is None: if desired_result is None:
# randomly choose desired_result # randomly choose desired_result
desired_result = np.random.choice([i for i in range(2 ** num_acted_qubits)], p=prob_amplitude) desired_result = np.random.choice(list(range(2**num_acted_qubits)), p=prob_amplitude)
else: else:
desired_result_str = bin(desired_result)[2:]
# check whether the state can collapse to desired_result # check whether the state can collapse to desired_result
assert prob_amplitude[desired_result] > 1e-20, ("it is infeasible for the state in qubits " + assert prob_amplitude[desired_result] > 1e-20, \
f"{self.qubits_idx} to collapse to state |{desired_result_str}>") f"it is infeasible for the state in qubits {self.qubits_idx} to collapse to state |{bin(desired_result)[2:]}>"
# retrieve the binary version of desired result # retrieve the binary version of desired result
desired_result_str = bin(desired_result)[2:] desired_result_str = bin(desired_result)[2:]
assert num_acted_qubits >= len(desired_result_str), "the desired_result is too large" assert num_acted_qubits >= len(desired_result_str), "the desired_result is too large"
for _ in range(num_acted_qubits - len(desired_result_str)): for _ in range(num_acted_qubits - len(desired_result_str)):
desired_result_str = '0' + desired_result_str desired_result_str = f'0{desired_result_str}'
# whether print the collapsed result # whether print the collapsed result
if self.if_print: if self.if_print:
# retrieve binary representation # retrieve binary representation
prob = prob_amplitude[desired_result].item() prob = prob_amplitude[desired_result].item()
print(f"qubits {self.qubits_idx} collapse to the state |{desired_result_str}> with probability {prob}") print(f"qubits {self.qubits_idx} collapse to the state |{desired_result_str}> with probability {prob}")
# determine projector according to the desired result local_projector = projector_gen()
local_projector = paddle.zeros([2 ** num_acted_qubits, 2 ** num_acted_qubits])
local_projector[desired_result, desired_result] += 1
local_projector = local_projector.cast(complex_dtype)
# apply the local projector and normalize it # apply the local projector and normalize it
if backend == Backend.StateVector: if backend == Backend.StateVector:
projected_state = state_vector.unitary_transformation(state.data, local_projector, self.qubits_idx, num_qubits) projected_state = state_vector.unitary_transformation(state.data, local_projector, self.qubits_idx, num_qubits)
return paddle_quantum.State(projected_state / (abs_norm(projected_state) + 0j))
else: else:
projected_state = density_matrix.unitary_transformation(state.data, local_projector, self.qubits_idx, num_qubits) projected_state = density_matrix.unitary_transformation(state.data, local_projector, self.qubits_idx, num_qubits)
return paddle_quantum.State(projected_state / paddle.trace(projected_state)) state = State(projected_state)
state.normalize()
return state
...@@ -14,27 +14,29 @@ ...@@ -14,27 +14,29 @@
# limitations under the License. # limitations under the License.
r""" r"""
The function for quantum information. The library of functions in quantum information.
""" """
import paddle_quantum
import math import math
import warnings
import re import re
import numpy as np import numpy as np
from scipy.linalg import logm, sqrtm from scipy.linalg import logm, sqrtm
import cvxpy
import matplotlib.image
import paddle import paddle
from paddle import kron, matmul, transpose import paddle_quantum as pq
from .state import State from .state import State, _type_fetch, _type_transform
from .base import get_dtype from .base import get_dtype
from .intrinsic import _get_float_dtype from .intrinsic import _get_float_dtype
from .linalg import dagger, is_unitary, NKron from .linalg import dagger, NKron, unitary_random
from .backend import Backend from .channel.custom import ChoiRepr, KrausRepr, StinespringRepr
import matplotlib.image
from typing import Optional, Tuple, List, Union from typing import Optional, Tuple, List, Union
def partial_trace(state: Union[State, paddle.Tensor], def partial_trace(state: Union[np.ndarray, paddle.Tensor, State],
dim1: int, dim2: int, A_or_B: int) -> Union[State, paddle.Tensor]: dim1: int, dim2: int, A_or_B: int) -> Union[np.ndarray, paddle.Tensor, State]:
r"""Calculate the partial trace of the quantum state. r"""Calculate the partial trace of the quantum state.
Args: Args:
...@@ -46,48 +48,25 @@ def partial_trace(state: Union[State, paddle.Tensor], ...@@ -46,48 +48,25 @@ def partial_trace(state: Union[State, paddle.Tensor],
Returns: Returns:
Partial trace of the input quantum state. Partial trace of the input quantum state.
""" """
if A_or_B == 2: type_str = _type_fetch(state)
dim1, dim2 = dim2, dim1 rho_AB = _type_transform(state, "density_matrix").data if type_str == "state_vector" \
else _type_transform(state, "tensor")
is_State = False
if isinstance(state, State): higher_dims = rho_AB.shape[:-2]
is_State = True new_state = paddle.trace(
backend = state.backend paddle.reshape(
rho_AB = state.data if backend != Backend.StateVector else state.ket @ state.bra rho_AB,
else: higher_dims.copy() + [dim1, dim2, dim1, dim2]
rho_AB = state ),
complex_dtype = rho_AB.dtype axis1 = -1 + A_or_B + len(higher_dims),
axis2 = 1 + A_or_B + len(higher_dims)
idty_B = paddle.eye(dim2).cast(complex_dtype) )
res = paddle.zeros([dim2, dim2]).cast(complex_dtype)
for dim_j in range(dim1):
row_top = paddle.zeros([1, dim_j])
row_mid = paddle.ones([1, 1])
row_bot = paddle.zeros([1, dim1 - dim_j - 1])
bra_j = paddle.concat([row_top, row_mid, row_bot], axis=1)
bra_j = paddle.cast(bra_j, complex_dtype)
if A_or_B == 1:
row_tmp = kron(bra_j, idty_B)
row_tmp_conj = paddle.conj(row_tmp)
res += (row_tmp @ rho_AB) @ paddle.transpose(row_tmp_conj, perm=[1, 0])
if A_or_B == 2:
row_tmp = kron(idty_B, bra_j)
row_tmp_conj = paddle.conj(row_tmp)
res += (row_tmp @ rho_AB) @ paddle.transpose(row_tmp_conj, perm=[1, 0])
if is_State: return _type_transform(new_state, type_str)
if backend == Backend.StateVector:
eigval, eigvec = paddle.linalg.eig(res)
res = eigvec[:, paddle.argmax(paddle.real(eigval))]
return State(res, backend=backend)
return res
def partial_trace_discontiguous(state: Union[State, paddle.Tensor], def partial_trace_discontiguous(state: Union[np.ndarray, paddle.Tensor, State],
preserve_qubits: list=None) -> Union[State, paddle.Tensor]: preserve_qubits: list=None) -> Union[np.ndarray, paddle.Tensor, State]:
r"""Calculate the partial trace of the quantum state with arbitrarily selected subsystem r"""Calculate the partial trace of the quantum state with arbitrarily selected subsystem
Args: Args:
...@@ -97,104 +76,87 @@ def partial_trace_discontiguous(state: Union[State, paddle.Tensor], ...@@ -97,104 +76,87 @@ def partial_trace_discontiguous(state: Union[State, paddle.Tensor],
Returns: Returns:
Partial trace of the quantum state with arbitrarily selected subsystem. Partial trace of the quantum state with arbitrarily selected subsystem.
""" """
is_State = False type_str = _type_fetch(state)
if isinstance(state, State): rho = _type_transform(state, "density_matrix").data if type_str == "state_vector" \
is_State = True else _type_transform(state, "tensor")
backend = rho.backend
rho = state.data if backend != Backend.StateVector else state.ket @ state.bra
else:
rho = state
complex_dtype = rho.dtype
if preserve_qubits is None: if preserve_qubits is None:
return rho return state
n = int(math.log2(rho.shape[-1]))
def new_partial_trace_singleOne(rho: paddle.Tensor, at: int) -> paddle.Tensor:
n_qubits = int(math.log2(rho.shape[-1]))
higher_dims = rho.shape[:-2]
rho = paddle.trace(
paddle.reshape(
rho,
higher_dims.copy() + [2 ** at, 2, 2 ** (n_qubits - at - 1), 2 ** at, 2, 2 ** (n_qubits - at - 1)]
),
axis1=1+len(higher_dims),
axis2=4+len(higher_dims)
)
return paddle.reshape(rho, higher_dims.copy() + [2 ** (n_qubits - 1), 2 ** (n_qubits - 1)])
n = int(math.log2(rho.size) // 2) for i, at in enumerate(x for x in range(n) if x not in preserve_qubits):
num_preserve = len(preserve_qubits) rho = new_partial_trace_singleOne(rho, at - i)
shape = paddle.ones((n + 1,)) return _type_transform(rho, type_str)
shape = 2 * shape
shape[n] = 2 ** n
shape = paddle.cast(shape, "int32")
identity = paddle.eye(2 ** n)
identity = paddle.reshape(identity, shape=shape)
discard = list()
for idx in range(0, n):
if idx not in preserve_qubits:
discard.append(idx)
addition = [n]
preserve_qubits.sort()
preserve_qubits = paddle.to_tensor(preserve_qubits)
discard = paddle.to_tensor(discard)
addition = paddle.to_tensor(addition)
permute = paddle.concat([discard, preserve_qubits, addition])
identity = paddle.transpose(identity, perm=permute)
identity = paddle.reshape(identity, (2 ** n, 2 ** n))
result = np.zeros((2 ** num_preserve, 2 ** num_preserve))
result = paddle.to_tensor(result, dtype=complex_dtype)
for i in range(0, 2 ** (n - num_preserve)):
bra = identity[i * 2 ** num_preserve:(i + 1) * 2 ** num_preserve, :]
result = result + matmul(matmul(bra, rho), transpose(bra, perm=[1, 0]))
if is_State:
if backend == Backend.StateVector:
eigval, eigvec = paddle.linalg.eig(result)
result = eigvec[:, paddle.argmax(paddle.real(eigval))]
return State(result, backend=backend)
return result
def trace_distance(rho: Union[State, paddle.Tensor], sigma: Union[State, paddle.Tensor]) -> paddle.Tensor: def trace_distance(rho: Union[np.ndarray, paddle.Tensor, State],
r"""Calculate the fidelity of two quantum states. sigma: Union[np.ndarray, paddle.Tensor, State]) -> Union[np.ndarray, paddle.Tensor]:
r"""Calculate the trace distance of two quantum states.
.. math:: .. math::
D(\rho, \sigma) = 1 / 2 * \text{tr}|\rho-\sigma| D(\rho, \sigma) = 1 / 2 * \text{tr}|\rho-\sigma|
Args: Args:
rho: Density matrix form of the quantum state. rho: a quantum state.
sigma: Density matrix form of the quantum state. sigma: a quantum state.
Returns: Returns:
The fidelity between the input quantum states. The trace distance between the input quantum states.
""" """
if isinstance(rho, State): type_rho, type_sigma = _type_fetch(rho), _type_fetch(sigma)
rho = rho.data rho = _type_transform(rho, "density_matrix").data
sigma = sigma.data sigma = _type_transform(sigma, "density_matrix").data
assert rho.shape == sigma.shape, 'The shape of two quantum states are different' assert rho.shape == sigma.shape, 'The shape of two quantum states are different'
eigval, eigvec = paddle.linalg.eig(rho - sigma) eigval, _ = paddle.linalg.eig(rho - sigma)
return 0.5 * paddle.sum(paddle.abs(eigval)) dist = 0.5 * paddle.sum(paddle.abs(eigval))
return dist.item() if type_rho == "numpy" and type_sigma == "numpy" else dist
def state_fidelity(rho: Union[State, paddle.Tensor], sigma: Union[State, paddle.Tensor]) -> paddle.Tensor: def state_fidelity(rho: Union[np.ndarray, paddle.Tensor, State],
sigma: Union[np.ndarray, paddle.Tensor, State]) -> Union[np.ndarray, paddle.Tensor]:
r"""Calculate the fidelity of two quantum states. r"""Calculate the fidelity of two quantum states.
.. math:: .. math::
F(\rho, \sigma) = \text{tr}(\sqrt{\sqrt{\rho}\sigma\sqrt{\rho}}) F(\rho, \sigma) = \text{tr}(\sqrt{\sqrt{\rho}\sigma\sqrt{\rho}})
Args: Args:
rho: Density matrix form of the quantum state. rho: a quantum state.
sigma: Density matrix form of the quantum state. sigma: a quantum state.
Returns: Returns:
The fidelity between the input quantum states. The fidelity between the input quantum states.
""" """
type_rho, type_sigma = _type_fetch(rho), _type_fetch(sigma)
rho = _type_transform(rho, "density_matrix").numpy()
sigma = _type_transform(sigma, "density_matrix").numpy()
if isinstance(rho, State):
rho = rho.data
sigma = sigma.data
rho = rho.numpy()
sigma = sigma.numpy()
assert rho.shape == sigma.shape, 'The shape of two quantum states are different' assert rho.shape == sigma.shape, 'The shape of two quantum states are different'
fidelity = np.trace(sqrtm(sqrtm(rho) @ sigma @ sqrtm(rho))).real fidelity = np.trace(sqrtm(sqrtm(rho) @ sigma @ sqrtm(rho))).real
if type_rho == "numpy" and type_sigma == "numpy":
return fidelity
return paddle.to_tensor(fidelity) return paddle.to_tensor(fidelity)
def gate_fidelity(U: paddle.Tensor, V: paddle.Tensor) -> paddle.Tensor: def gate_fidelity(U: Union[np.ndarray, paddle.Tensor],
V: Union[np.ndarray, paddle.Tensor]) -> Union[np.ndarray, paddle.Tensor]:
r"""calculate the fidelity between gates r"""calculate the fidelity between gates
.. math:: .. math::
...@@ -211,15 +173,17 @@ def gate_fidelity(U: paddle.Tensor, V: paddle.Tensor) -> paddle.Tensor: ...@@ -211,15 +173,17 @@ def gate_fidelity(U: paddle.Tensor, V: paddle.Tensor) -> paddle.Tensor:
fidelity between gates fidelity between gates
""" """
complex_dtype = U.dtype type_u, type_v = _type_fetch(U), _type_fetch(V)
V = paddle.cast(V, dtype=complex_dtype) U, V = _type_transform(U, "tensor"), _type_transform(V, "tensor")
V = paddle.cast(V, dtype=U.dtype)
assert U.shape == V.shape, 'The shape of two matrices are different' assert U.shape == V.shape, 'The shape of two matrices are different'
fidelity = paddle.abs(paddle.trace(U @ dagger(V))) / U.shape[0] fidelity = paddle.abs(paddle.trace(U @ dagger(V))) / U.shape[0]
return fidelity
return fidelity.item() if type_u == "numpy" or type_v == "numpy" else fidelity
def purity(rho: Union[State, paddle.Tensor]) -> paddle.Tensor: def purity(rho: Union[np.ndarray, paddle.Tensor, State]) -> Union[np.ndarray, paddle.Tensor]:
r"""Calculate the purity of a quantum state. r"""Calculate the purity of a quantum state.
.. math:: .. math::
...@@ -232,14 +196,14 @@ def purity(rho: Union[State, paddle.Tensor]) -> paddle.Tensor: ...@@ -232,14 +196,14 @@ def purity(rho: Union[State, paddle.Tensor]) -> paddle.Tensor:
Returns: Returns:
The purity of the input quantum state. The purity of the input quantum state.
""" """
if isinstance(rho, State): type_rho = _type_fetch(rho)
rho = rho.data rho = _type_transform(rho, "density_matrix").data
gamma = paddle.trace(paddle.matmul(rho, rho)) gamma = paddle.trace(rho @ rho).real()
return gamma.real() return gamma.item() if type_rho == "numpy" else gamma
def von_neumann_entropy(rho: Union[State, paddle.Tensor]) -> paddle.Tensor: def von_neumann_entropy(rho: Union[np.ndarray, paddle.Tensor, State], base: Optional[int] = 2) -> Union[np.ndarray, paddle.Tensor]:
r"""Calculate the von Neumann entropy of a quantum state. r"""Calculate the von Neumann entropy of a quantum state.
.. math:: .. math::
...@@ -248,46 +212,44 @@ def von_neumann_entropy(rho: Union[State, paddle.Tensor]) -> paddle.Tensor: ...@@ -248,46 +212,44 @@ def von_neumann_entropy(rho: Union[State, paddle.Tensor]) -> paddle.Tensor:
Args: Args:
rho: Density matrix form of the quantum state. rho: Density matrix form of the quantum state.
base: The base of logarithm. Defaults to 2.
Returns: Returns:
The von Neumann entropy of the input quantum state. The von Neumann entropy of the input quantum state.
""" """
if isinstance(rho, State): type_rho = _type_fetch(rho)
rho = rho.data rho = _type_transform(rho, "density_matrix").data
rho = rho.numpy() rho_eigenvalues = paddle.real(paddle.linalg.eigvals(rho))
rho_eigenvalues = np.real(np.linalg.eigvals(rho)) entropy = -1 * (math.log(math.e, base)) * sum([eigenvalue * paddle.log(eigenvalue) for eigenvalue in rho_eigenvalues if eigenvalue >= 1e-8])
entropy = 0
for eigenvalue in rho_eigenvalues:
if np.abs(eigenvalue) < 1e-8:
continue
entropy -= eigenvalue * np.log(eigenvalue)
return paddle.to_tensor(entropy) return entropy.item() if type_rho == "numpy" else entropy
def relative_entropy(rho: Union[State, paddle.Tensor], sig: Union[State, paddle.Tensor]) -> paddle.Tensor: def relative_entropy(rho: Union[np.ndarray, paddle.Tensor, State],
sig: Union[np.ndarray, paddle.Tensor, State], base: Optional[int] = 2) -> Union[np.ndarray, paddle.Tensor]:
r"""Calculate the relative entropy of two quantum states. r"""Calculate the relative entropy of two quantum states.
.. math:: .. math::
S(\rho \| \sigma)=\text{tr} \rho(\log \rho-\log \sigma) S(\rho \| \sigma)=\text{tr} \rho(\log \rho-\log \sigma)
Args: Args:
rho: Density matrix form of the quantum state. rho: Density matrix form of the quantum state.
sig: Density matrix form of the quantum state. sig: Density matrix form of the quantum state.
base: The base of logarithm. Defaults to 2.
Returns: Returns:
Relative entropy between input quantum states. Relative entropy between input quantum states.
""" """
if isinstance(rho, State): type_rho, type_sig = _type_fetch(rho), _type_fetch(sig)
rho = rho.data rho = _type_transform(rho, "density_matrix").numpy()
sig = sig.data sig = _type_transform(sig, "density_matrix").numpy()
rho = rho.numpy()
sig = sig.numpy()
assert rho.shape == sig.shape, 'The shape of two quantum states are different' assert rho.shape == sig.shape, 'The shape of two quantum states are different'
res = np.trace(rho @ logm(rho) - rho @ logm(sig)) entropy = (math.log(math.e, base)) * np.trace(rho @ logm(rho) - rho @ logm(sig)).real
return paddle.to_tensor(res.real)
if type_rho == "numpy" or type_sig == "numpy":
return entropy
return paddle.to_tensor(entropy)
def random_pauli_str_generator(n: int, terms: Optional[int] = 3) -> List: def random_pauli_str_generator(n: int, terms: Optional[int] = 3) -> List:
...@@ -352,9 +314,7 @@ def pauli_str_to_matrix(pauli_str: list, n: int) -> paddle.Tensor: ...@@ -352,9 +314,7 @@ def pauli_str_to_matrix(pauli_str: list, n: int) -> paddle.Tensor:
# Convert new_pauli_str to matrix; 'xziiy' to NKron(x, z, i, i, y) # Convert new_pauli_str to matrix; 'xziiy' to NKron(x, z, i, i, y)
matrices = [] matrices = []
for coeff, op_str in new_pauli_str: for coeff, op_str in new_pauli_str:
sub_matrices = [] sub_matrices = [pauli_dict[op.lower()] for op in op_str]
for op in op_str:
sub_matrices.append(pauli_dict[op.lower()])
if len(op_str) == 1: if len(op_str) == 1:
matrices.append(coeff * sub_matrices[0]) matrices.append(coeff * sub_matrices[0])
else: else:
...@@ -366,23 +326,41 @@ def pauli_str_to_matrix(pauli_str: list, n: int) -> paddle.Tensor: ...@@ -366,23 +326,41 @@ def pauli_str_to_matrix(pauli_str: list, n: int) -> paddle.Tensor:
return paddle.to_tensor(sum(matrices), dtype=get_dtype()) return paddle.to_tensor(sum(matrices), dtype=get_dtype())
def partial_transpose_2(density_op: Union[State, paddle.Tensor], sub_system: int = None) -> paddle.Tensor: def partial_transpose_2(density_op: Union[np.ndarray, paddle.Tensor, State],
sub_system: int = None) -> Union[np.ndarray, paddle.Tensor]:
r"""Calculate the partial transpose :math:`\rho^{T_A}` of the input quantum state. r"""Calculate the partial transpose :math:`\rho^{T_A}` of the input quantum state.
Args: Args:
density_op: Density matrix form of the quantum state. density_op: Density matrix form of the quantum state.
sub_system: 1 or 2. 1 means to perform partial transpose on system A; 2 means to perform partial trace on system B. Default is 2. sub_system: 1 or 2. 1 means to perform partial transpose on system A;
2 means to perform partial transpose on system B. Default is 2.
Returns: Returns:
The partial transpose of the input quantum state. The partial transpose of the input quantum state.
Example:
.. code-block:: python
import paddle
from paddle_quantum.qinfo import partial_transpose_2
rho_test = paddle.arange(1,17).reshape([4,4])
partial_transpose_2(rho_test, sub_system=1)
::
[[ 1, 2, 9, 10],
[ 5, 6, 13, 14],
[ 3, 4, 11, 12],
[ 7, 8, 15, 16]]
""" """
type_str = _type_fetch(density_op)
density_op = _type_transform(density_op, "density_matrix").numpy()
sys_idx = 2 if sub_system is None else 1 sys_idx = 2 if sub_system is None else 1
# Copy the density matrix and not corrupt the original one # Copy the density matrix and not corrupt the original one
if isinstance(density_op, State):
density_op = density_op.data
complex_dtype = density_op.dtype
density_op = density_op.numpy()
transposed_density_op = np.copy(density_op) transposed_density_op = np.copy(density_op)
if sys_idx == 2: if sys_idx == 2:
for j in [0, 2]: for j in [0, 2]:
...@@ -392,33 +370,36 @@ def partial_transpose_2(density_op: Union[State, paddle.Tensor], sub_system: int ...@@ -392,33 +370,36 @@ def partial_transpose_2(density_op: Union[State, paddle.Tensor], sub_system: int
transposed_density_op[2:4, 0:2] = density_op[0:2, 2:4] transposed_density_op[2:4, 0:2] = density_op[0:2, 2:4]
transposed_density_op[0:2, 2:4] = density_op[2:4, 0:2] transposed_density_op[0:2, 2:4] = density_op[2:4, 0:2]
return paddle.to_tensor(transposed_density_op, dtype=complex_dtype) if type_str == "numpy":
return transposed_density_op
return paddle.to_tensor(transposed_density_op)
def partial_transpose(density_op: Union[State, paddle.Tensor], n: int) -> paddle.Tensor: def partial_transpose(density_op: Union[np.ndarray, paddle.Tensor, State],
n: int) -> Union[np.ndarray, paddle.Tensor]:
r"""Calculate the partial transpose :math:`\rho^{T_A}` of the input quantum state. r"""Calculate the partial transpose :math:`\rho^{T_A}` of the input quantum state.
Args: Args:
density_op: Density matrix form of the quantum state. density_op: Density matrix form of the quantum state.
n: Number of qubits of the system to be transposed. n: Number of qubits of subsystem A, with qubit indices as [0, 1, ..., n-1]
Returns: Returns:
The partial transpose of the input quantum state. The partial transpose of the input quantum state.
""" """
# Copy the density matrix and not corrupt the original one # Copy the density matrix and not corrupt the original one
if isinstance(density_op, State): type_str = _type_fetch(density_op)
density_op = density_op.data density_op = _type_transform(density_op, "density_matrix")
complex_dtype = density_op.dtype n_qubits = density_op.num_qubits
density_op = density_op.numpy() density_op = density_op.data
transposed_density_op = np.copy(density_op)
for j in range(0, 2 ** n, 2):
for i in range(0, 2 ** n, 2):
transposed_density_op[i:i + 2, j:j + 2] = density_op[i:i + 2, j:j + 2].transpose()
return paddle.to_tensor(transposed_density_op, dtype=complex_dtype) density_op = paddle.reshape(density_op, [2 ** n, 2 ** (n_qubits - n), 2 ** n, 2 ** (n_qubits - n)])
density_op = paddle.transpose(density_op, [2, 1, 0, 3])
density_op = paddle.reshape(density_op, [2 ** n_qubits, 2 ** n_qubits])
return density_op.numpy() if type_str == "numpy" else density_op
def negativity(density_op: Union[State, paddle.Tensor]) -> paddle.Tensor:
def negativity(density_op: Union[np.ndarray, paddle.Tensor, State]) -> Union[np.ndarray, paddle.Tensor]:
r"""Compute the Negativity :math:`N = ||\frac{\rho^{T_A}-1}{2}||` of the input quantum state. r"""Compute the Negativity :math:`N = ||\frac{\rho^{T_A}-1}{2}||` of the input quantum state.
Args: Args:
...@@ -429,9 +410,8 @@ def negativity(density_op: Union[State, paddle.Tensor]) -> paddle.Tensor: ...@@ -429,9 +410,8 @@ def negativity(density_op: Union[State, paddle.Tensor]) -> paddle.Tensor:
""" """
# Implement the partial transpose # Implement the partial transpose
density_op_T = partial_transpose_2(density_op) density_op_T = partial_transpose_2(density_op)
if isinstance(density_op_T, State): type_str = _type_fetch(density_op_T)
density_op_T = density_op_T.data density_op = _type_transform(density_op_T, "density_matrix").numpy()
density_op_T = density_op_T.numpy()
# Calculate through the equivalent expression N = sum(abs(\lambda_i)) when \lambda_i<0 # Calculate through the equivalent expression N = sum(abs(\lambda_i)) when \lambda_i<0
n = 0.0 n = 0.0
...@@ -439,10 +419,11 @@ def negativity(density_op: Union[State, paddle.Tensor]) -> paddle.Tensor: ...@@ -439,10 +419,11 @@ def negativity(density_op: Union[State, paddle.Tensor]) -> paddle.Tensor:
for val in eigen_val: for val in eigen_val:
if val < 0: if val < 0:
n = n + np.abs(val) n = n + np.abs(val)
return paddle.to_tensor(n, dtype=_get_float_dtype(paddle_quantum.get_dtype()))
return n if type_str == "numpy" else paddle.to_tensor(n)
def logarithmic_negativity(density_op: Union[State, paddle.Tensor]) -> paddle.Tensor: def logarithmic_negativity(density_op: Union[np.ndarray, paddle.Tensor, State]) -> Union[np.ndarray, paddle.Tensor]:
r"""Calculate the Logarithmic Negativity :math:`E_N = ||\rho^{T_A}||` of the input quantum state. r"""Calculate the Logarithmic Negativity :math:`E_N = ||\rho^{T_A}||` of the input quantum state.
Args: Args:
...@@ -454,12 +435,10 @@ def logarithmic_negativity(density_op: Union[State, paddle.Tensor]) -> paddle.Te ...@@ -454,12 +435,10 @@ def logarithmic_negativity(density_op: Union[State, paddle.Tensor]) -> paddle.Te
# Calculate the negativity # Calculate the negativity
n = negativity(density_op) n = negativity(density_op)
# Calculate through the equivalent expression return paddle.log2(2 * n + 1)
log2_n = paddle.log2(2 * n + 1)
return log2_n
def is_ppt(density_op: Union[State, paddle.Tensor]) -> bool: def is_ppt(density_op: Union[np.ndarray, paddle.Tensor, State]) -> bool:
r"""Check if the input quantum state is PPT. r"""Check if the input quantum state is PPT.
Args: Args:
...@@ -468,17 +447,12 @@ def is_ppt(density_op: Union[State, paddle.Tensor]) -> bool: ...@@ -468,17 +447,12 @@ def is_ppt(density_op: Union[State, paddle.Tensor]) -> bool:
Returns: Returns:
Whether the input quantum state is PPT. Whether the input quantum state is PPT.
""" """
# By default the PPT condition is satisfied return bool(negativity(density_op) <= 0)
ppt = True
# Detect negative eigenvalues from the partial transposed density_op
if negativity(density_op) > 0:
ppt = False
return ppt
def schmidt_decompose(psi: Union[State, paddle.Tensor], def schmidt_decompose(psi: Union[np.ndarray, paddle.Tensor, State],
sys_A: List[int]=None) -> Tuple[paddle.Tensor, paddle.Tensor, paddle.Tensor]: sys_A: List[int]=None) -> Union[Tuple[paddle.Tensor, paddle.Tensor, paddle.Tensor],
Tuple[np.ndarray, np.ndarray, np.ndarray]]:
r"""Calculate the Schmidt decomposition of a quantum state :math:`\lvert\psi\rangle=\sum_ic_i\lvert i_A\rangle\otimes\lvert i_B \rangle`. r"""Calculate the Schmidt decomposition of a quantum state :math:`\lvert\psi\rangle=\sum_ic_i\lvert i_A\rangle\otimes\lvert i_B \rangle`.
Args: Args:
...@@ -492,16 +466,14 @@ def schmidt_decompose(psi: Union[State, paddle.Tensor], ...@@ -492,16 +466,14 @@ def schmidt_decompose(psi: Union[State, paddle.Tensor],
* A high dimensional array composed of bases for subsystem A :math:`\lvert i_A\rangle`, with shape ``(k, 2**m, 1)`` * A high dimensional array composed of bases for subsystem A :math:`\lvert i_A\rangle`, with shape ``(k, 2**m, 1)``
* A high dimensional array composed of bases for subsystem B :math:`\lvert i_B\rangle` , with shape ``(k, 2**m, 1)`` * A high dimensional array composed of bases for subsystem B :math:`\lvert i_B\rangle` , with shape ``(k, 2**m, 1)``
""" """
if isinstance(psi, State): type_psi = _type_fetch(psi)
psi = psi.data psi = _type_transform(psi, "state_vector").numpy()
psi = psi.numpy()
complex_dtype = psi.dtype
assert psi.ndim == 1, 'Psi must be a one dimensional vector.' assert psi.ndim == 1, 'Psi must be a one dimensional vector.'
assert np.log2(psi.size).is_integer(), 'The number of amplitutes must be an integral power of 2.' assert np.log2(psi.size).is_integer(), 'The number of amplitutes must be an integral power of 2.'
tot_qu = int(np.log2(psi.size)) tot_qu = int(np.log2(psi.size))
sys_A = sys_A if sys_A is not None else [i for i in range(tot_qu//2)] sys_A = sys_A if sys_A is not None else list(range(tot_qu//2))
sys_B = [i for i in range(tot_qu) if i not in sys_A] sys_B = [i for i in range(tot_qu) if i not in sys_A]
# Permute qubit indices # Permute qubit indices
...@@ -514,10 +486,11 @@ def schmidt_decompose(psi: Union[State, paddle.Tensor], ...@@ -514,10 +486,11 @@ def schmidt_decompose(psi: Union[State, paddle.Tensor],
u, c, v = np.linalg.svd(amp_mtr) u, c, v = np.linalg.svd(amp_mtr)
k = np.count_nonzero(c > 1e-13) k = np.count_nonzero(c > 1e-13)
c = paddle.to_tensor(c[:k], dtype=complex_dtype) c, u, v = c[:k], u.T[:k].reshape([k, -1, 1]), v[:k].reshape([k, -1, 1])
u = paddle.to_tensor(u.T[:k].reshape([k, -1, 1]), dtype=complex_dtype)
v = paddle.to_tensor(v[:k].reshape([k, -1, 1]), dtype=complex_dtype) if type_psi == "numpy":
return c, u, v return c, u, v
return paddle.to_tensor(c), paddle.to_tensor(u), paddle.to_tensor(v)
def image_to_density_matrix(image_filepath: str) -> State: def image_to_density_matrix(image_filepath: str) -> State:
...@@ -540,10 +513,10 @@ def image_to_density_matrix(image_filepath: str) -> State: ...@@ -540,10 +513,10 @@ def image_to_density_matrix(image_filepath: str) -> State:
# Density matrix whose trace is 1 # Density matrix whose trace is 1
rho = image_matrix@image_matrix.T rho = image_matrix@image_matrix.T
rho = rho/np.trace(rho) rho = rho/np.trace(rho)
return State(paddle.to_tensor(rho), backend=paddle_quantum.Backend.DensityMatrix) return State(rho, backend=pq.Backend.DensityMatrix)
def shadow_trace(state: 'State', hamiltonian: paddle_quantum.Hamiltonian, def shadow_trace(state: 'State', hamiltonian: pq.Hamiltonian,
sample_shots: int, method: Optional[str] = 'CS') -> float: sample_shots: int, method: Optional[str] = 'CS') -> float:
r"""Estimate the expectation value :math:`\text{trace}(H\rho)` of an observable :math:`H`. r"""Estimate the expectation value :math:`\text{trace}(H\rho)` of an observable :math:`H`.
...@@ -551,7 +524,7 @@ def shadow_trace(state: 'State', hamiltonian: paddle_quantum.Hamiltonian, ...@@ -551,7 +524,7 @@ def shadow_trace(state: 'State', hamiltonian: paddle_quantum.Hamiltonian,
state: Quantum state. state: Quantum state.
hamiltonian: Observable. hamiltonian: Observable.
sample_shots: Number of samples. sample_shots: Number of samples.
method: Method used to , which should be one of “CS”, “LBCS”, and “APS”. Default is “CS”. method: Method used to, which should be one of “CS”, “LBCS”, and “APS”. Default is “CS”.
Raises: Raises:
ValueError: Hamiltonian has a bad form ValueError: Hamiltonian has a bad form
...@@ -564,12 +537,12 @@ def shadow_trace(state: 'State', hamiltonian: paddle_quantum.Hamiltonian, ...@@ -564,12 +537,12 @@ def shadow_trace(state: 'State', hamiltonian: paddle_quantum.Hamiltonian,
num_qubits = state.num_qubits num_qubits = state.num_qubits
mode = state.backend mode = state.backend
if method == "LBCS": if method == "LBCS":
result, beta = paddle_quantum.shadow.shadow_sample(state, num_qubits, sample_shots, mode, hamiltonian, method) result, beta = pq.shadow.shadow_sample(state, num_qubits, sample_shots, mode, hamiltonian, method)
else: else:
result = paddle_quantum.shadow.shadow_sample(state, num_qubits, sample_shots, mode, hamiltonian, method) result = pq.shadow.shadow_sample(state, num_qubits, sample_shots, mode, hamiltonian, method)
def prepare_hamiltonian(hamiltonian, num_qubits): def prepare_hamiltonian(hamiltonian, num_qubits):
new_hamiltonian = list() new_hamiltonian = []
for idx, (coeff, pauli_str) in enumerate(hamiltonian): for idx, (coeff, pauli_str) in enumerate(hamiltonian):
pauli_str = re.split(r',\s*', pauli_str.lower()) pauli_str = re.split(r',\s*', pauli_str.lower())
pauli_term = ['i'] * num_qubits pauli_term = ['i'] * num_qubits
...@@ -630,7 +603,7 @@ def shadow_trace(state: 'State', hamiltonian: paddle_quantum.Hamiltonian, ...@@ -630,7 +603,7 @@ def shadow_trace(state: 'State', hamiltonian: paddle_quantum.Hamiltonian,
# Define the functions required by APS # Define the functions required by APS
def is_covered(pauli, pauli_str): def is_covered(pauli, pauli_str):
for qubit_idx in range(num_qubits): for qubit_idx in range(num_qubits):
if not pauli[qubit_idx] in ('i', pauli_str[qubit_idx]): if pauli[qubit_idx] not in ('i', pauli_str[qubit_idx]):
return False return False
return True return True
...@@ -686,7 +659,7 @@ def shadow_trace(state: 'State', hamiltonian: paddle_quantum.Hamiltonian, ...@@ -686,7 +659,7 @@ def shadow_trace(state: 'State', hamiltonian: paddle_quantum.Hamiltonian,
return trace_estimation return trace_estimation
def tensor_product(state_a: Union[State, paddle.Tensor], state_b: Union[State, paddle.Tensor], *args: Union[State, paddle.Tensor]) -> State: def tensor_state(state_a: State, state_b: State, *args: State) -> State:
r"""calculate tensor product (kronecker product) between at least two state. This function automatically returns State instance r"""calculate tensor product (kronecker product) between at least two state. This function automatically returns State instance
Args: Args:
...@@ -694,37 +667,182 @@ def tensor_product(state_a: Union[State, paddle.Tensor], state_b: Union[State, p ...@@ -694,37 +667,182 @@ def tensor_product(state_a: Union[State, paddle.Tensor], state_b: Union[State, p
state_b: State state_b: State
*args: other states *args: other states
Returns:
tensor product state of input states
Notes:
Need to be careful with the backend of states;
Use ``paddle_quantum.linalg.NKron`` if the input datatype is ``paddle.Tensor`` or ``numpy.ndarray``.
"""
state_a, state_b = _type_transform(state_a, "tensor"), _type_transform(state_b, "tensor")
return State(NKron(state_a, state_b, [_type_transform(st, "tensor") for st in args]))
def diamond_norm(channel_repr: Union[ChoiRepr, KrausRepr, StinespringRepr, paddle.Tensor],
dim_io: Union[int, Tuple[int, int]] = None, **kwargs) -> float:
r'''Calculate the diamond norm of input.
Args:
channel_repr: A ``ChoiRepr`` or ``KrausRepr`` or ``StinespringRepr`` instance or a ``paddle.Tensor`` instance.
dim_io: The input and output dimensions.
**kwargs: Parameters to set cvx.
Raises: Raises:
NotImplementedError: only accept state or tensor instances RuntimeError: `channel_repr` must be `ChoiRepr`or `KrausRepr` or `StinespringRepr` or `paddle.Tensor`.
TypeError: "dim_io" should be "int" or "tuple".
Warning:
`channel_repr` is not in Choi representaiton, and is converted into `ChoiRepr`.
Returns: Returns:
tensor product of input states Its diamond norm.
Reference:
Khatri, Sumeet, and Mark M. Wilde. "Principles of quantum communication theory: A modern approach."
arXiv preprint arXiv:2011.04672 (2020).
Watrous, J. . "Semidefinite Programs for Completely Bounded Norms."
Theory of Computing 5.1(2009):217-238.
'''
if isinstance(channel_repr, ChoiRepr):
choi_matrix = channel_repr.choi_oper
elif isinstance(channel_repr, paddle.Tensor):
choi_matrix = channel_repr
elif isinstance(channel_repr, (KrausRepr, StinespringRepr)):
warnings.warn('`channel_repr` is not in Choi representaiton, and is converted into `ChoiRepr`')
choi_matrix = channel_convert(channel_repr, 'Choi').choi_oper
else:
raise RuntimeError('`channel_repr` must be `ChoiRepr`or `KrausRepr` or `StinespringRepr` or `paddle.Tensor`.')
if dim_io is None: # Default to dim_in == dim_out
dim_in = dim_out = int(math.sqrt(choi_matrix.shape[0]))
elif isinstance(dim_io, tuple):
dim_in = int(dim_io[0])
dim_out = int(dim_io[1])
elif isinstance(dim_io, int):
dim_in = dim_io
dim_out = dim_io
else:
raise TypeError('"dim_io" should be "int" or "tuple".')
kron_size = dim_in * dim_out
# Cost function : Trace( \Omega @ Choi_matrix )
rho = cvxpy.Variable(shape=(dim_in, dim_in), complex=True)
omega = cvxpy.Variable(shape=(kron_size, kron_size), complex=True)
identity = np.eye(dim_out)
# \rho \otimes 1 \geq \Omega
cons_matrix = cvxpy.kron(rho, identity) - omega
cons = [
rho >> 0,
rho.H == rho,
cvxpy.trace(rho) == 1,
omega >> 0,
omega.H == omega,
cons_matrix >> 0
]
obj = cvxpy.Maximize(2 * cvxpy.real((cvxpy.trace(omega @ choi_matrix))))
prob = cvxpy.Problem(obj, cons)
return prob.solve(**kwargs)
def channel_convert(original_channel: Union[ChoiRepr, KrausRepr, StinespringRepr], target: str, tol: float = 1e-6) -> Union[ChoiRepr, KrausRepr, StinespringRepr]:
r"""convert the given channel to the target implementation
Args:
original_channel: input quantum channel
target: target implementation, should to be ``Choi``, ``Kraus`` or ``Stinespring``
tol: error tolerance of the convert, 1e-6 by default
Raises:
ValueError: Unsupported channel representation: require Choi, Kraus or Stinespring.
Returns:
Union[ChoiRepr, KrausRepr]: quantum channel by the target implementation
Note: Note:
the backend should be density matrix. choi -> kraus currently has the error of order 1e-6 caused by eigh
Raises:
NotImplementedError: does not support the conversion of input data type
""" """
if isinstance(state_a, State): target = target.capitalize()
data_a = state_a.data if target not in ['Choi', 'Kraus', 'Stinespring']:
elif isinstance(state_a, paddle.Tensor): raise ValueError(f"Unsupported channel representation: require Choi, Kraus or Stinespring, not {target}")
data_a = state_a if type(original_channel).__name__ == f'{target}Repr':
else: return original_channel
raise NotImplementedError
if isinstance(original_channel, KrausRepr) and target == 'Choi':
if isinstance(state_b, State): kraus_oper = original_channel.kraus_oper
data_b = state_b.data ndim = original_channel.kraus_oper[0].shape[0]
elif isinstance(state_b, paddle.Tensor): kraus_oper_tensor = paddle.concat([paddle.kron(x, x.conj().T) for x in kraus_oper]).reshape([len(kraus_oper), ndim, -1])
data_b = state_b chan = paddle.sum(kraus_oper_tensor, axis=0).reshape([ndim for _ in range(4)]).transpose([2, 1, 0, 3])
choi_repr = chan.transpose([0, 2, 1, 3]).reshape([ndim * ndim, ndim * ndim])
result = ChoiRepr(choi_repr, original_channel.qubits_idx)
result.qubits_idx = original_channel.qubits_idx
elif isinstance(original_channel, KrausRepr) and target == 'Stinespring':
kraus_oper = original_channel.kraus_oper
j_dim = len(kraus_oper)
i_dim = kraus_oper[0].shape[0]
kraus_oper_tensor = paddle.concat(kraus_oper).reshape([j_dim, i_dim, -1])
stinespring_mat = kraus_oper_tensor.transpose([1, 0, 2])
stinespring_mat = stinespring_mat.reshape([i_dim * j_dim, i_dim])
result = StinespringRepr(stinespring_mat, original_channel.qubits_idx)
result.qubits_idx = original_channel.qubits_idx
elif isinstance(original_channel, ChoiRepr) and target == 'Kraus':
choi_repr = original_channel.choi_oper
ndim = int(math.sqrt(original_channel.choi_oper.shape[0]))
w, v = paddle.linalg.eigh(choi_repr)
# add abs to make eigvals safe
w = paddle.abs(w)
l_cut = 0
for l in range(len(w) - 1, -1, -1):
if paddle.sum(paddle.abs(w[l:])) / paddle.sum(paddle.abs(w)) > 1 - tol:
l_cut = l
break
kraus = [(v * paddle.sqrt(w))[:, l].reshape([ndim, ndim]).T for l in range(l_cut, ndim**2)]
result = KrausRepr(kraus, original_channel.qubits_idx)
result.qubits_idx = original_channel.qubits_idx
elif isinstance(original_channel, StinespringRepr) and target == 'Kraus':
stinespring_mat = original_channel.stinespring_matrix
i_dim = stinespring_mat.shape[1]
j_dim = stinespring_mat.shape[0] // i_dim
kraus_oper = stinespring_mat.reshape([i_dim, j_dim, i_dim]).transpose([1, 0, 2])
kraus_oper = [kraus_oper[j] for j in range(j_dim)]
result = KrausRepr(kraus_oper, original_channel.qubits_idx)
result.qubits_idx = original_channel.qubits_idx
else: else:
raise NotImplementedError raise NotImplementedError(
f"does not support the conversion of {type(original_channel)}")
addis = []
for st in args:
if isinstance(st, State):
addis.append(st.data)
elif isinstance(st, paddle.Tensor):
addis.append(st)
else:
raise NotImplementedError
res = paddle_quantum.linalg.NKron(data_a, data_b, *addis) return result
return State(res)
\ No newline at end of file
def kraus_oper_random(num_qubits: int, num_oper: int) -> list:
r""" randomly generate a set of kraus operators for quantum channel
Args:
num_qubits: The amount of qubits of quantum channel.
num_oper: The amount of kraus operators to be generated.
Returns:
a list of kraus operators
"""
float_dtype = _get_float_dtype(get_dtype())
prob = [paddle.sqrt(paddle.to_tensor(1/num_oper, dtype = float_dtype))] * num_oper
return [prob[idx] * unitary_random(num_qubits) for idx in range(num_oper)]
# !/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"""
The quantum machine learning module.
"""
# !/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"""
The VSQL model.
"""
import random
from typing import Optional, List, Tuple
import numpy as np
import paddle
import paddle.nn.functional as F
from paddle.vision.datasets import MNIST
import paddle_quantum as pq
from paddle_quantum.ansatz import Circuit
def norm_image(images: List[np.ndarray], num_qubits: int) -> List[paddle.Tensor]:
r"""
Normalize the input images. Flatten them and make them to normalized vectors.
Args:
images: The input images.
num_qubits: The number of qubits, which decides the dimension of the vector.
Returns:
Return the normalized vectors, which is the list of paddle's tensors.
"""
# pad and normalize the image
_images = []
for image in images:
image = image.flatten()
if image.size < 2 ** num_qubits:
_images.append(np.pad(image, pad_width=(0, 2 ** num_qubits - image.size)))
else:
_images.append(image[:2 ** num_qubits])
return [paddle.to_tensor(image / np.linalg.norm(image), dtype=pq.get_dtype()) for image in _images]
def data_loading(
num_qubits: int, mode: str, classes: list, num_data: Optional[int] = None
) -> Tuple[List[np.ndarray], List[int]]:
r"""
Loading the MNIST dataset, which only contains the specified data.
Args:
num_qubits: The number of qubits, which determines the dimension of the normalized vector.
mode: Specifies the loaded dataset: ``'train'`` | ``'test'`` .
- ``'train'`` : Load the training dataset.
- ``'test'`` : Load the test dataset.
classes: The labels of the data which will be loaded.
num_data: The number of data to be loaded. Defaults to ``None``, which means loading all data.
Returns:
Return the loaded dataset, which is ``(images, labels)`` .
"""
data = MNIST(mode=mode, backend='cv2')
filtered_data = [item for item in data if item[1].item() in classes]
random.shuffle(filtered_data)
if num_data is None:
num_data = len(filtered_data)
images = [filtered_data[idx][0] for idx in range(min(len(filtered_data), num_data))]
labels = [filtered_data[idx][1] for idx in range(min(len(filtered_data), num_data))]
images = norm_image(images, num_qubits=num_qubits)
labels = [label.item() for label in labels]
return images, labels
def _slide_circuit(cir: pq.ansatz.Circuit, distance: int):
# slide to get the local feature
for sublayer in cir.sublayers():
qubits_idx = np.array(sublayer.qubits_idx)
qubits_idx = qubits_idx + distance
sublayer.qubits_idx = qubits_idx.tolist()
def observable(start_idx: int, num_shadow: int) -> pq.Hamiltonian:
r"""
Generate the observable to measure the quantum states.
Args:
start_idx: The start index of the qubits.
num_shadow: The number of qubits which the shadow circuit contains.
Returns:
Return the generated observable.
"""
# construct the observable to get te output of the circuit
pauli_str = ','.join(f'x{str(i)}' for i in range(start_idx, start_idx + num_shadow))
return pq.Hamiltonian([[1.0, pauli_str]])
class VSQL(paddle.nn.Layer):
r"""
The class of the variational shadow quantum learning (VSQL).
The details can be referred to https://ojs.aaai.org/index.php/AAAI/article/view/17016 .
Args:
num_qubits: The number of qubits which the quantum circuit contains.
num_shadow: The number of qubits which the shadow circuit contains.
num_classes: The number of class which the modell will classify.
depth: The depth of the quantum circuit. Defaults to ``1`` .
"""
def __init__(self, num_qubits: int, num_shadow: int, num_classes: int, depth: int = 1):
super().__init__()
self.num_qubits = num_qubits
self.num_shadow = num_shadow
self.depth = depth
cir = Circuit(num_qubits)
for idx in range(num_shadow):
cir.rx(qubits_idx=idx)
cir.ry(qubits_idx=idx)
cir.rx(qubits_idx=idx)
for _ in range(depth):
for idx in range(num_shadow - 1):
cir.cnot([idx, idx + 1])
cir.cnot([num_shadow - 1, 0])
for idx in range(num_shadow):
cir.ry(qubits_idx=idx)
self.cir = cir
self.fc = paddle.nn.Linear(
num_qubits - num_shadow + 1, num_classes,
weight_attr=paddle.ParamAttr(initializer=paddle.nn.initializer.Normal()),
bias_attr=paddle.ParamAttr(initializer=paddle.nn.initializer.Normal())
)
def forward(self, batch_input: List[paddle.Tensor]) -> paddle.Tensor:
r"""
The forward function.
Args:
batch_input: The input of the model. It's shape is :math:`(\text{batch_size}, 2^{\text{num_qubits}})` .
Returns:
Return the output of the model. It's shape is :math:`(\text{batch_size}, \text{num_classes})` .
"""
batch_feature = []
for input in batch_input:
_state = pq.State(input)
feature = []
for idx_start in range(self.num_qubits - self.num_shadow + 1):
ob = observable(idx_start, num_shadow=self.num_shadow)
_slide_circuit(cir=self.cir, distance=1 if idx_start != 0 else 0)
expec_val_func = pq.loss.ExpecVal(ob)
out_state = self.cir(_state)
expec_val = expec_val_func(out_state)
feature.append(expec_val)
# slide the circuit to the initial position
_slide_circuit(self.cir, -idx_start)
feature = paddle.concat(feature)
batch_feature.append(feature)
batch_feature = paddle.stack(batch_feature)
return self.fc(batch_feature)
def train(
num_qubits: int, num_shadow: int, depth: int = 1,
batch_size: int = 16, epoch: int = 10, learning_rate: float = 0.01,
classes: Optional[list] = None, num_train: Optional[int] = None, num_test: Optional[int] = None
) -> None:
"""
The function of training the VSQL model.
Args:
num_qubits: The number of qubits which the quantum circuit contains.
num_shadow: The number of qubits which the shadow circuit contains.
depth: The depth of the quantum circuit. Defaults to ``1`` .
batch_size: The size of the batch samplers. Defaults to ``16`` .
epoch: The number of epochs to train the model. Defaults to ``10`` .
learning_rate: The learning rate used to update the parameters. Defaults to ``0.01`` .
classes: The classes of handwrite digits to be predicted.
Defaults to ``None`` , which means predict all the classes.
num_train: The number of the data in the training dataset.
Defaults to ``None`` , which will use all training data.
num_test: The number of the data in the test dataset. Defaults to ``None`` , which will use all test data.
"""
if classes is None:
classes = list(range(10))
train_input, train_label = data_loading(num_qubits=num_qubits, mode='train', classes=classes, num_data=num_train)
test_input, test_label = data_loading(num_qubits=num_qubits, mode='test', classes=classes, num_data=num_test)
net = VSQL(num_qubits, num_shadow=num_shadow, num_classes=len(classes), depth=depth)
opt = paddle.optimizer.Adam(learning_rate=learning_rate, parameters=net.parameters())
num_train = len(train_label) if num_train is None else num_train
num_test = len(test_label) if num_test is None else num_test
for idx_epoch in range(epoch):
for itr in range(num_train // batch_size):
output = net(train_input[itr * batch_size:(itr + 1) * batch_size])
labels = paddle.to_tensor(train_label[itr * batch_size:(itr + 1) * batch_size])
loss = F.cross_entropy(output, labels)
loss.backward()
opt.minimize(loss)
opt.clear_grad()
if itr % 10 == 0:
predictions = paddle.argmax(output, axis=-1).tolist()
labels = labels.tolist()
train_acc = sum(labels[idx] == predictions[idx] for idx in range(len(labels))) / len(labels)
output = net(test_input[:num_test])
labels = test_label[:num_test]
predictions = paddle.argmax(output, axis=-1).tolist()
test_acc = sum(labels[idx] == predictions[idx] for idx in range(len(labels))) / num_test
print(
f"Epoch: {idx_epoch: 3d}, iter: {itr: 3d}, loss: {loss.item(): .4f}, "
f"batch_acc: {train_acc: .2%}, test_acc: {test_acc: .2%}."
)
state_dict = net.state_dict()
paddle.save(state_dict, 'vsql.pdparams')
if __name__ == '__main__':
train(
num_qubits=10,
num_shadow=2,
depth=1,
batch_size=20,
epoch=10,
learning_rate=0.01,
classes=[0, 1],
num_train=1000,
num_test=100
)
# !/usr/bin/env python3
# Copyright (c) 2021 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"""
The module of quantum phase processing.
"""
from .laurent import *
from .angles import *
from .utils import *
# !/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.
import numpy as np
from paddle_quantum.ansatz import Circuit
from copy import copy
from typing import Optional, Tuple, List
from math import atan, cos, sin
from .laurent import Laurent
r"""
QPP angle solver for trigonometric QSP, see Lemma 3 in paper https://arxiv.org/abs/2205.07848 for more details.
"""
__all__ = ['qpp_angle_finder', 'qpp_angle_approximator']
def qpp_angle_finder(P: Laurent, Q: Laurent) -> Tuple[List[float], List[float]]:
r"""Find the corresponding set of angles for a Laurent pair `P`, `Q`.
Args:
P: a Laurent poly.
Q: a Laurent poly.
Returns:
contains the following elements:
- list_theta: angles for :math:`R_Y` gates;
- list_phi: angles for :math:`R_Z` gates.
"""
# input check
P, Q = copy(P), copy(Q)
condition_test(P, Q)
list_theta = []
list_phi = []
# backup P for output check
P_copy = copy(P)
L = P.deg
while L > 0:
theta, phi = update_angle([P.coef[-1], P.coef[0], Q.coef[-1], Q.coef[0]])
list_theta.append(theta)
list_phi.append(phi)
P, Q = update_polynomial(P, Q, theta, phi)
L = P.deg
# decide theta[0], phi[0] and global phase alpha
p_0, q_0 = P.coef[0], Q.coef[0]
alpha, theta, phi = yz_decomposition(np.array([[p_0, -q_0], [np.conj(q_0), np.conj(p_0)]]))
list_theta.append(theta)
list_phi.append(phi)
# test outputs, by 5 random data points in [-pi, pi]
err_list = []
list_x = (np.random.rand(5) * 2 - 1) * np.pi
for x in list_x:
experiment_y = matrix_generation(list_theta, list_phi, x, alpha)[0, 0]
actual_y = P_copy(np.exp(1j * x / 2))
err = np.abs(experiment_y + actual_y) if np.abs(experiment_y / actual_y + 1) < 1e-2 else np.abs(experiment_y - actual_y)
if err > 0.1:
print(experiment_y)
print(actual_y)
raise ValueError(
f"oversized error: {err}, check your code")
err_list.append(err)
print(f"computations of angles for QPP are completed with mean error {np.mean(err_list)}")
return list_theta, list_phi
def qpp_angle_approximator(P: Laurent, Q: Laurent) -> Tuple[List[float], List[float]]:
r"""Approximate the corresponding set of angles for a Laurent pair `P`, `Q`.
Args:
P: a Laurent poly.
Q: a Laurent poly.
Returns:
contains the following elements:
- list_theta: angles for :math:`R_Y` gates;
- list_phi: angles for :math:`R_Z` gates.
Note:
unlike `yzzyz_angle_finder`,
`yzzyz_angle_approximator` assumes that the only source of error is the precision (which is not generally true).
"""
list_theta = []
list_phi = []
# backup P for output check
P_copy = copy(P)
L = P.deg
while L > 0:
theta, phi = update_angle([P.coef[-1], P.coef[0], Q.coef[-1], Q.coef[0]])
list_theta.append(theta)
list_phi.append(phi)
P, Q = update_polynomial(P, Q, theta, phi, verify=False)
L -= 1
P, Q = P.reduced_poly(L), Q.reduced_poly(L)
# decide theta[0], phi[0] and global phase alpha
p_0, q_0 = P.coef[0], Q.coef[0]
alpha, theta, phi = yz_decomposition(np.array([[p_0, -q_0], [np.conj(q_0), np.conj(p_0)]]))
list_theta.append(theta)
list_phi.append(phi)
# test outputs, by 5 random data points in [-pi, pi]
err_list = []
list_x = (np.random.rand(5) * 2 - 1) * np.pi
for x in list_x:
experiment_y = matrix_generation(list_theta, list_phi, x, alpha)[0, 0]
actual_y = P_copy(np.exp(1j * x / 2))
err = np.abs(experiment_y + actual_y) if np.abs(experiment_y / actual_y + 1) < 1e-2 else np.abs(experiment_y - actual_y)
err_list.append(err)
print(f"Computations of angles for QPP are completed with mean error {np.mean(err_list)}")
return list_theta, list_phi
# ------------------------------------------------- Split line -------------------------------------------------
r"""
Belows are support functions for angles computation.
"""
def update_angle(coef: List[complex]) -> Tuple[float, float]:
r"""Compute angles by `coef` from `P` and `Q`.
Args:
coef: the first and last terms from `P` and `Q`.
Returns:
`theta` and `phi`.
"""
# with respect to the first and last terms of P and Q, respectively
p_d, p_nd, q_d, q_nd = coef[0], coef[1], coef[2], coef[3]
if p_d != 0 and q_d != 0:
val = -1 * p_d / q_d
return atan(np.abs(val)) * 2, np.real(np.log(val / np.abs(val)) / (-1j))
elif np.abs(p_d) < 1e-25 and np.abs(q_d) < 1e-25:
val = q_nd / p_nd
return atan(np.abs(val)) * 2, np.real(np.log(val / np.abs(val)) / (-1j))
elif np.abs(p_d) < 1e-25 and np.abs(q_nd) < 1e-25:
return 0, 0
elif np.abs(p_nd) < 1e-25 and np.abs(q_d) < 1e-25:
return np.pi, 0
raise ValueError(
f"Coef error: check these four coef {[p_d, p_nd, q_d, q_nd]}")
def update_polynomial(P: Laurent, Q: Laurent, theta: float, phi: float, verify: Optional[bool] = True) -> Tuple[Laurent, Laurent]:
r"""Update `P` and `Q` by `theta` and `phi`.
Args:
P: a Laurent poly.
Q: a Laurent poly.
theta: a param.
phi: a param.
verify: whether verify the correctness of computation, defaults to be `True`.
Returns:
updated `P` and `Q`.
"""
phi_hf = phi / 2
theta_hf = theta / 2
X = Laurent([0, 0, 1])
inv_X = Laurent([1, 0, 0])
new_P = (X * P * np.exp(1j * phi_hf) * cos(theta_hf)) + (X * Q * np.exp(-1j * phi_hf) * sin(theta_hf))
new_Q = (inv_X * Q * np.exp(-1j * phi_hf) * cos(theta_hf)) - (inv_X * P * np.exp(1j * phi_hf) * sin(theta_hf))
if not verify:
return new_P, new_Q
condition_test(new_P, new_Q)
return new_P, new_Q
def condition_test(P: Laurent, Q: Laurent) -> None:
r"""Check whether `P` and `Q` satisfy:
- deg(`P`) = deg(`Q`);
- `P` 和 `Q` have the same parity;
- :math:`PP^* + QQ^* = 1`.
Args:
P: a Laurent poly.
Q: a Laurent poly.
"""
L = P.deg
if L != Q.deg:
print("The last and first terms of P: ", P.coef[0], P.coef[-1])
print("The last and first terms of Q: ", Q.coef[0], Q.coef[-1])
raise ValueError(f"P's degree {L} does not agree with Q's degree {Q.deg}")
if P.parity != Q.parity or P.parity != L % 2:
print(f"P's degree is {L}")
raise ValueError(f"P's parity {P.parity} and Q's parity {Q.parity}) should be both {L % 2}")
poly_one = (P * P.conj) + (Q * Q.conj)
if poly_one != 1:
print(f"P's degree is {L}")
print("the last and first terms of PP* + QQ*: ", poly_one.coef[0], poly_one.coef[-1])
raise ValueError("PP* + QQ* != 1: check your code")
def matrix_generation(list_theta: List[float], list_phi: List[float], x: float, alpha: Optional[float] = 0) -> np.ndarray:
r"""Return the matrix generated by sets of angles.
Args:
list_theta: angles for :math:`R_Y` gates.
list_phi: angles for :math:`R_Z` gates.
x: input of polynomial P
alpha: global phase
Returns:
unitary matrix generated by YZZYZ circuit
"""
assert len(list_theta) == len(list_phi)
L = len(list_theta) - 1
cir = Circuit(1)
for i in range(L):
cir.rz(0, param=list_phi[i])
cir.ry(0, param=list_theta[i])
cir.rz(0, param=x) # input x
cir.rz(0, param=list_phi[-1])
cir.ry(0, param=list_theta[-1])
return cir.unitary_matrix().numpy() * alpha
def yz_decomposition(U: np.ndarray) -> Tuple[complex, float, float]:
r"""Return the YZ decomposition of U.
Args:
U: single-qubit unitary.
Returns:
`alpha`, `theta`, `phi` st. :math:`U[0, 0] = \alpha R_Y(\theta) R_Z(\phi) [0, 0]`.
"""
a, b = U[0, 0], U[0, 1]
x, y, p, q = np.real(a), np.imag(a), np.real(b), np.imag(b)
phi = np.pi if x == p == 0 else np.arctan(-y / x) - np.arctan(-q / p)
theta = 2 * np.arctan(np.sqrt((p ** 2 + q ** 2) / (x ** 2 + y ** 2)))
alpha = -a / (cos(phi / 2) * cos(theta / 2) - 1j * sin(phi / 2) * cos(theta / 2))
assert np.abs(np.abs(alpha) - 1) < 1e-5, f"Calculation error for absolute global phase {np.abs(alpha)}, check your code."
if np.abs((cos(theta / 2) * np.exp(1j * phi / 2)) * alpha / a + 1) < 1e-6:
alpha = -alpha
return alpha, theta, phi
\ No newline at end of file
# !/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.
import numpy as np
import warnings
from numpy.polynomial.polynomial import Polynomial, polyfromroots
from scipy.special import jv as Bessel
from typing import Any, Callable, List, Optional, Tuple, Union
r"""
Definition of ``Laurent`` class and its functions
"""
__all__ = ['Laurent', 'revise_tol', 'remove_abs_error', 'random_laurent_poly',
'sqrt_generation', 'Q_generation', 'pair_generation',
'laurent_generator', 'hamiltonian_laurent', 'ln_laurent', 'power_laurent']
TOL = 1e-30 # the error tolerance for Laurent polynomial, default to be machinery
class Laurent(object):
r"""Class for Laurent polynomial defined as
:math:`P:\mathbb{C}[X, X^{-1}] \to \mathbb{C} :x \mapsto \sum_{j = -L}^{L} p_j X^j`.
Args:
coef: list of coefficients of Laurent poly, arranged as :math:`\{p_{-L}, ..., p_{-1}, p_0, p_1, ..., p_L\}`.
"""
def __init__(self, coef: np.ndarray) -> None:
if not isinstance(coef, np.ndarray):
coef = np.array(coef)
coef = coef.astype('complex128')
coef = remove_abs_error(np.squeeze(coef) if len(coef.shape) > 1 else coef)
assert len(coef.shape) == 1 and coef.shape[0] % 2 == 1
# if the first and last terms are both 0, remove them
while len(coef) > 1 and coef[0] == coef[-1] == 0:
coef = coef[1:-1]
# decide degree of this poly
L = (len(coef) - 1) // 2 if len(coef) > 1 else 0
# rearrange the coef in order p_0, ..., p_L, p_{-L}, ..., p_{-1},
# then we can call ``poly_coef[i]`` to retrieve p_i, this order is for internal use only
coef = coef.tolist()
poly_coef = np.array(coef[L:] + coef[:L]).astype('complex128')
self.deg = L
self.__coef = poly_coef
def __call__(self, X: Union[int, float, complex]) -> complex:
r"""Evaluate the value of P(X).
"""
if X == 0:
return self.__coef[0]
return sum(self.__coef[i] * (X ** i) for i in range(-self.deg, self.deg + 1))
@property
def coef(self) -> np.ndarray:
r"""The coefficients of this polynomial in ascending order (of indices).
"""
return ascending_coef(self.__coef)
@property
def conj(self) -> 'Laurent':
r"""The conjugate of this polynomial i.e. :math:`P(x) = \sum_{j = -L}^{L} p_{-j}^* X^j`.
"""
coef = np.copy(self.__coef)
for i in range(1, self.deg + 1):
coef[i], coef[-i] = coef[-i], coef[i]
coef = np.conj(coef)
return Laurent(ascending_coef(coef))
@property
def roots(self) -> List[complex]:
r"""List of roots of this polynomial.
"""
# create the corresponding (common) polynomial with degree 2L
P = Polynomial(self.coef)
roots = P.roots().tolist()
return sorted(roots, key=lambda x: np.abs(x))
@property
def norm(self) -> float:
r"""The square sum of absolute value of coefficients of this polynomial.
"""
return np.sum(np.square(np.abs(self.__coef)))
@property
def max_norm(self) -> float:
r"""The maximum of absolute value of coefficients of this polynomial.
"""
list_x = np.exp(-1j * np.arange(-np.pi, np.pi + 0.005, 0.005) / 2)
return max(np.abs(self(x)) for x in list_x)
@property
def parity(self) -> int:
r""" Parity of this polynomial.
"""
coef = np.copy(self.__coef)
even = not any(i % 2 != 0 and coef[i] != 0 for i in range(-self.deg, self.deg + 1))
odd = not any(i % 2 != 1 and coef[i] != 0 for i in range(-self.deg, self.deg + 1))
if even:
return 0
return 1 if odd else None
def __copy__(self) -> 'Laurent':
r"""Copy of Laurent polynomial.
"""
return Laurent(ascending_coef(self.__coef))
def __add__(self, other: Any) -> 'Laurent':
r"""Addition of Laurent polynomial.
Args:
other: scalar or a Laurent polynomial :math:`Q(x) = \sum_{j = -L}^{L} q_{j} X^j`.
"""
coef = np.copy(self.__coef)
if isinstance(other, (int, float, complex)):
coef[0] += other
elif isinstance(other, Laurent):
if other.deg > self.deg:
return other + self
deg_diff = self.deg - other.deg
# retrieve the coef of Q
q_coef = other.coef
q_coef = np.concatenate([q_coef[other.deg:], np.zeros(2 * deg_diff),
q_coef[:other.deg]]).astype('complex128')
coef += q_coef
else:
raise TypeError(
f"does not support the addition between Laurent and {type(other)}.")
return Laurent(ascending_coef(coef))
def __mul__(self, other: Any) -> 'Laurent':
r"""Multiplication of Laurent polynomial.
Args:
other: scalar or a Laurent polynomial :math:`Q(x) = \sum_{j = -L}^{L} q_{j} X^j`.
"""
p_coef = np.copy(self.__coef)
if isinstance(other, (int, float, complex)):
new_coef = p_coef * other
elif isinstance(other, Laurent):
# retrieve the coef of Q
q_coef = other.coef.tolist()
q_coef = np.array(q_coef[other.deg:] + q_coef[:other.deg]).astype('complex128')
L = self.deg + other.deg # deg of new poly
new_coef = np.zeros([2 * L + 1]).astype('complex128')
# (P + Q)[X^n] = \sum_{j, k st. j + k = n} p_j q_k
for j in range(-self.deg, self.deg + 1):
for k in range(-other.deg, other.deg + 1):
new_coef[j + k] += p_coef[j] * q_coef[k]
else:
raise TypeError(
f"does not support the multiplication between Laurent and {type(other)}.")
return Laurent(ascending_coef(new_coef))
def __sub__(self, other: Any) -> 'Laurent':
r"""Subtraction of Laurent polynomial.
Args:
other: scalar or a Laurent polynomial :math:`Q(x) = \sum_{j = -L}^{L} q_{j} X^j`.
"""
return self.__add__(other=other * -1)
def __eq__(self, other: Any) -> bool:
r"""Equality of Laurent polynomial.
Args:
other: a Laurent polynomial :math:`Q(x) = \sum_{j = -L}^{L} q_{j} X^j`.
"""
if isinstance(other, (int, float, complex)):
p_coef = self.__coef
constant_term = p_coef[0]
return self.deg == 0 and np.abs(constant_term - other) < 1e-6
elif isinstance(other, Laurent):
p_coef = self.coef
q_coef = other.coef
return self.deg == other.deg and np.max(np.abs(p_coef - q_coef)) < 1e-6
else:
raise TypeError(
f"does not support the equality between Laurent and {type(other)}.")
def __str__(self) -> str:
r"""Print of Laurent polynomial.
"""
coef = np.around(self.__coef, 3)
L = self.deg
print_str = "info of this Laurent poly is as follows\n"
print_str += f" - constant: {coef[0]} - degree: {L}\n"
if L > 0:
print_str += f" - coef of terms from pos 1 to pos {L}: {coef[1:L + 1]}\n"
print_str += f" - coef of terms from pos -1 to pos -{L}: {np.flip(coef[L + 1:])}\n"
return print_str
def is_parity(self, p: int) -> Tuple[bool, complex]:
r"""Whether this Laurent polynomial has parity :math:`p % 2`.
Args:
p: parity.
Returns
contains the following elements:
* whether parity is p % 2;
* if not, then return the the (maximum) absolute coef term breaking such parity;
* if not, then return the the (minimum) absolute coef term obeying such parity.
"""
p %= 2
coef = np.copy(self.__coef)
disagree_coef = []
agree_coef = []
for i in range(-self.deg, self.deg + 1):
c = coef[i]
if i % 2 != p and c != 0:
disagree_coef.append(c)
elif i % 2 == p:
agree_coef.append(c)
return (False, max(np.abs(disagree_coef)), min(np.abs(agree_coef))) if disagree_coef else (True, None, None)
def reduced_poly(self, target_deg: int) -> 'Laurent':
r"""Generate :math:`P'(x) = \sum_{j = -D}^{D} p_j X^j`, where :math:`D \leq L` is `target_deg`.
Args:
target_deg: the degree of returned polynomial
"""
coef = self.coef
L = self.deg
return Laurent(coef[L - target_deg:L + 1 + target_deg]) if target_deg <= L else Laurent(coef)
# ------------------------------------------------- Split line -------------------------------------------------
r"""
Belows are support functions for `Laurent` class
"""
def revise_tol(t: float) -> None:
r"""Revise the value of `TOL`.
"""
global TOL
assert t > 0
TOL = t
def ascending_coef(coef: np.ndarray) -> np.ndarray:
r"""Transform the coefficients of a polynomial in ascending order (of indices).
Args:
coef: list of coefficients arranged as :math:`\{ p_0, ..., p_L, p_{-L}, ..., p_{-1} \}`.
Returns:
list of coefficients arranged as :math:`\{ p_{-L}, ..., p_{-1}, p_0, p_1, ..., p_L \}`.
"""
L = int((len(coef) - 1) / 2)
coef = coef.tolist()
return np.array(coef[L + 1:] + coef[:L + 1])
def remove_abs_error(data: np.ndarray, tol: Optional[float] = None) -> np.ndarray:
r"""Remove the error in data array.
Args:
data: data array.
tol: error tolerance.
Returns:
sanitized data.
"""
data_len = len(data)
tol = TOL if tol is None else tol
for i in range(data_len):
if np.abs(np.real(data[i])) < tol:
data[i] = 1j * np.imag(data[i])
elif np.abs(np.imag(data[i])) < tol:
data[i] = np.real(data[i])
if np.abs(data[i]) < tol:
data[i] = 0
return data
# ------------------------------------------------- Split Line -------------------------------------------------
r"""
Belows are some functions using `Laurent` class
"""
def random_laurent_poly(deg: int, parity: Optional[int] = None, is_real: Optional[bool] = False) -> Laurent:
r"""Randomly generate a Laurent polynomial.
Args:
deg: degree of this poly.
parity: parity of this poly, defaults to be `None`.
is_real: whether coefficients of this poly are real, defaults to be `False`.
Returns:
a Laurent poly with norm less than or equal to 1.
"""
real = np.random.rand(deg * 2 + 1) * 2 - 1
imag = np.zeros(deg * 2 + 1) if is_real else np.random.rand(deg * 2 + 1) * 2 - 1
coef = real + 1j * imag
coef /= np.sum(np.abs(coef))
if parity is not None:
coef = coef.tolist()
coef = coef[deg:] + coef[:deg]
for i in range(-deg, deg + 1):
if i % 2 != parity:
coef[i] = 0
coef = np.array(coef[deg + 1:] + coef[:deg + 1])
return Laurent(coef)
def sqrt_generation(A: Laurent) -> Laurent:
r"""Generate the "square root" of a Laurent polynomial :math:`A`.
Args:
A: a Laurent polynomial.
Returns:
a Laurent polynomial :math:`Q` such that :math:`QQ^* = A`.
Notes:
More details are in Lemma S1 of the paper https://arxiv.org/abs/2209.14278.
"""
leading_coef = A.coef[-1]
roots = A.roots
roots_dict = dict({})
def has_key(y: complex) -> Tuple[bool, complex]:
r"""Test whether `y` is the key of roots_dict.
Returns:
contains the following elements:
* boolean for whether `y` is the key of roots_dict.
* the key matched (under certain error tolerance) or `None`.
"""
list_key = list(roots_dict.keys())
for key in list_key:
# you can adjust this tolerance if the below assertion fails
if np.abs(key - y) < 1e-6:
return True, key
return False, None
# begin filtering roots
inv_roots = []
for x in roots:
inv_x = 1 / np.conj(x)
is_key, key = has_key(x)
# if x match with a existed key, save x
if is_key:
assert not has_key(inv_x)[0], \
f"{x} and {inv_x} should not be in the same list, check your code; it is perhaps a precision problem."
roots_dict[key] += 1
# if neither x nor its inverse conjugate match, save x
elif not has_key(inv_x)[0]:
roots_dict[x] = 1
# otherwise (i.e. inv_x with a existed key, save x), filter x
else:
inv_roots.append(x)
# 1/x^* should be filtered from the list of roots, now update the roots of Q
Q_roots = []
for key in roots_dict:
for _ in range(roots_dict[key]):
Q_roots.append(key)
# be careful that the number of filtered roots should be identical with that of the saved roots
if len(Q_roots) != len(inv_roots):
warnings.warn(
"\nError occurred in square root decomposition of polynomial: " +
f"# of total, saved and filtered roots are {len(roots)}, {len(Q_roots)}, {len(inv_roots)}." +
"\n Will force equal size of saved and filtered root list to mitigate the error")
excess_roots, Q_roots = Q_roots[len(inv_roots):], Q_roots[:len(inv_roots)]
excess_roots.sort(key=lambda x: np.real(x)) # sort by real part
for i in range(len(excess_roots) // 2):
Q_roots.append(excess_roots[2 * i])
inv_roots.append(excess_roots[2 * i + 1])
inv_roots = np.array(inv_roots)
# construct Q
Q_coef = polyfromroots(Q_roots) * np.sqrt(leading_coef * np.prod(inv_roots))
Q = Laurent(Q_coef)
# final output test
if Q * Q.conj != A:
warnings.warn(
f"\ncomputation error: QQ* != A, check your code \n degree of Q: {Q.deg}, degree of A: {A.deg}")
return Q
def Q_generation(P: Laurent) -> Laurent:
r"""Generate a Laurent complement for Laurent polynomial :math:`P`.
Args:
P: a Laurent poly with parity :math:`L` and degree :math:`L`.
Returns:
a Laurent poly :math:`Q` st. :math:`PP^* + QQ^* = 1`, with parity :math:`L` and degree :math:`L`.
"""
assert P.parity is not None and P.parity == P.deg % 2, \
"this Laurent poly does not satisfy the requirement for parity"
assert P.max_norm < 1, \
f"the max norm {P.max_norm} of this Laurent poly should be smaller than 1"
Q2 = P * P.conj * -1 + 1
Q = sqrt_generation(Q2)
is_parity, max_diff, min_val = Q.is_parity(P.parity)
if not is_parity:
warnings.warn(
f"\nQ's parity {Q.parity} does not agree with P's parity {P.parity}, max err is {max_diff}, min val is {min_val}")
return Q
def pair_generation(f: Laurent) -> Laurent:
r""" Generate Laurent pairs for Laurent polynomial :math:`f`.
Args:
f: a real-valued and even Laurent polynomial, with max_norm smaller than 1.
Returns:
Laurent polys :math:`P, Q` st. :math:`P = \sqrt{1 + f / 2}, Q = \sqrt{1 - f / 2}`.
"""
assert f.max_norm < 1, \
f"the max norm {f.max_norm} of this Laurent poly should be smaller than 1"
assert f.parity == 0, \
"the parity of this Laurent poly should be 0"
expect_parity = (f.deg // 2) % 2
P, Q = sqrt_generation((f + 1) * 0.5), sqrt_generation((f * (-1) + 1) * 0.5)
is_parity, max_diff, min_val = P.is_parity(expect_parity)
if not is_parity:
warnings.warn(
f"\nP's parity {P.parity} does not agree with {expect_parity}, max err is {max_diff}, min val is {min_val}", UserWarning)
is_parity, max_diff, min_val = Q.is_parity(expect_parity)
if not is_parity:
warnings.warn(
f"\nQ's parity {Q.parity} does not agree with {expect_parity}, max err is {max_diff}, min val is {min_val}", UserWarning)
return P, Q
# ------------------------------------------------- Split line -------------------------------------------------
r"""
Belows are tools for trigonometric approximation.
"""
def laurent_generator(fn: Callable[[np.ndarray], np.ndarray], dx: float, deg: int, L: float) -> Laurent:
r"""Generate a Laurent polynomial (with :math:`X = e^{ix / 2}`) approximating `fn`.
Args:
fn: function to be approximated.
dx: sampling frequency of data points.
deg: degree of Laurent poly.
L: half of approximation width.
Returns:
a Laurent polynomial approximating `fn` in interval :math:`[-L, L]` with degree `deg`.
"""
assert dx > 0 and L > 0 and deg >= 0
N = 2 * L / dx
coef = np.zeros(deg + 1, dtype=np.complex128)
xk = np.arange(-L, L + dx, dx)
# Calculate the coefficients for each term
for mi in range(deg + 1):
n = mi - deg / 2
coef[mi] = 1 / N * sum(fn(xk) * np.exp(-1j * n * np.pi * xk / L))
coef = np.array([coef[i // 2] if i % 2 == 0 else 0 for i in range(deg * 2 + 1)])
return Laurent(coef)
def deg_finder(fn: Callable[[np.ndarray], np.ndarray],
delta: Optional[float] = 0.00001 * np.pi, l: Optional[float] = np.pi) -> int:
r"""Find a degree such that the Laurent polynomial generated from `laurent_generator` has max_norm smaller than 1.
Args:
fn: function to be approximated.
dx: sampling frequency of data points, defaults to be :math:`0.00001 \pi`.
L: half of approximation width, defaults to be :math:`\pi`.
Returns:
the degree of approximation:
Notes:
used to fix the problem of function `laurent_generator`.
"""
deg = 50
acc = 1
P = laurent_generator(fn, delta, deg, l)
while P.max_norm > 1:
deg += acc * 50
P = laurent_generator(fn, delta, deg, l)
acc += 1
assert deg <= 10000, "degree too large"
return deg
def step_laurent(deg: int) -> Laurent:
r"""Generate a Laurent polynomial approximating the step function.
Args:
deg: (even) degree of the output Laurent poly.
Returns:
a Laurent poly approximating :math:`f(x) = 0.5` if :math:`x <= 0` else :math:`0`.
Notes:
used in Hamiltonian energy solver
"""
assert deg % 2 == 0
deg //= 2
coef = np.zeros(2 * deg + 1).astype('complex128')
for n in range(-deg, deg + 1):
if n != 0:
coef[n] = (0.025292684335809737j +
0.11253953951963828j * np.exp(-np.pi * 1j * n) -
0.13783222385544802j * np.exp(np.pi * 1j * n)) / n
else:
coef[n] = 0.7865660924854931
coef = ascending_coef(coef)
coef = np.array([coef[i // 2] if i % 2 == 0 else 0 for i in range(4 * deg + 1)])
return Laurent(coef)
def hamiltonian_laurent(t: float, deg: int) -> Laurent:
r"""Generate a Laurent polynomial approximating the Hamiltonian evolution function.
Args:
t: evolution constant (time).
deg: (even) degree of the output Laurent poly.
Returns:
a Laurent poly approximating :math:`e^{it \cos(x)}`.
Note:
- originated from the Jacobi-Anger expansion: :math:`y(x) = \sum_n i^n Bessel(n, x) e^{inx}`;
- used in Hamiltonian simulation.
"""
assert deg % 2 == 0
deg //= 2
coef = np.zeros(2 * deg + 1).astype('complex128')
for n in range(-deg, deg + 1):
coef[n] = (1j ** n) * Bessel(n, t)
coef = ascending_coef(coef)
coef = np.array([coef[i // 2] if i % 2 == 0 else 0 for i in range(4 * deg + 1)])
return Laurent(coef)
def ln_laurent(deg: int, t: float) -> Laurent:
r"""Generate a Laurent polynomial approximating the ln function.
Args:
deg: degree of Laurent polynomial that is a factor of 4.
t: normalization factor.
Returns:
a Laurent poly approximating :math:`ln(cos(x)^2) / t`.
Notes:
used in von Neumann entropy estimation.
"""
assert deg % 4 == 0
deg //= 2
coef = np.zeros(2 * deg + 1).astype('complex128')
for k in range(1, deg + 1):
for j in range(2 * k + 1):
coef[2 * j - 2 * k] += ((-1) ** (k + j + 1)) / t / k * (0.25 ** k) * comb(2 * k, j)
coef = ascending_coef(coef)
coef = np.array([coef[i // 2] if i % 2 == 0 else 0 for i in range(4 * deg + 1)])
return Laurent(coef)
def comb(n: float, k: int) -> float:
r"""Compute nCr(n, k).
"""
prod = 1
for i in range(k):
prod *= (n - i) / (k - i)
return prod
def power_laurent(deg: int, alpha: float, t: float) -> Laurent:
r"""Generate a Laurent polynomial approximating the power function.
Args:
deg: degree of Laurent polynomial that is a factor of 4.
alpha: the # of power.
t: normalization factor.
Returns:
a Laurent poly approximating :math:`(cos(x)^2)^{\alpha / 2} / t`.
"""
assert deg % 4 == 0 and alpha != 0 and alpha > -1
alpha /= 2
deg //= 2
coef = np.zeros(2 * deg + 1).astype('complex128')
for k in range(deg + 1):
for j in range(2 * k + 1):
coef[2 * j - 2 * k] += ((-1) ** j) / t * comb(alpha, k) * (0.25 ** k) * comb(2 * k, j)
coef = ascending_coef(coef)
coef = np.array([coef[i // 2] if i % 2 == 0 else 0 for i in range(4 * deg + 1)])
return Laurent(coef)
# !/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.
import numpy as np
import paddle
from typing import Callable, Optional, Tuple, Union
from math import log2
from .angles import qpp_angle_finder, qpp_angle_approximator
from .laurent import Q_generation, pair_generation, laurent_generator, revise_tol
from ..ansatz import Circuit
from ..backend import Backend
from ..base import get_dtype
from ..gate import X
from ..intrinsic import _get_float_dtype
from ..linalg import unitary_random, is_unitary
from ..loss import Measure
from ..operator import Collapse
from ..qinfo import dagger, partial_trace
from ..state import State, _type_transform, zero_state
r"""
QPP circuit and related tools, see Theorem 6 in paper https://arxiv.org/abs/2209.14278 for more details.
"""
__all__ = ["qpp_cir", "simulation_cir", "qps", "qubitize", "purification_block_enc"]
def qpp_cir(
list_theta: Union[np.ndarray, paddle.Tensor],
list_phi: Union[np.ndarray, paddle.Tensor],
U: Union[np.ndarray, paddle.Tensor, float],
) -> Circuit:
r"""Construct a quantum phase processor of QPP by `list_theta` and `list_phi`.
Args:
list_theta: angles for :math:`R_Y` gates.
list_phi: angles for :math:`R_Z` gates.
U: unitary or scalar input.
Returns:
a multi-qubit version of trigonometric QSP.
"""
complex_dtype = get_dtype()
float_dtype = _get_float_dtype(complex_dtype)
if not isinstance(list_theta, paddle.Tensor):
list_theta = paddle.to_tensor(list_theta, dtype=float_dtype)
if not isinstance(list_phi, paddle.Tensor):
list_phi = paddle.to_tensor(list_phi, dtype=float_dtype)
if not isinstance(U, paddle.Tensor):
U = paddle.to_tensor(U, dtype=complex_dtype)
if len(U.shape) == 1:
U = U.cast(float_dtype)
list_theta, list_phi = np.squeeze(list_theta), np.squeeze(list_phi)
assert len(list_theta) == len(list_phi)
n = int(log2(U.shape[0]))
L = len(list_theta) - 1
cir = Circuit(n + 1)
all_register = list(range(n + 1))
for i in range(L):
cir.rz(0, param=list_phi[i])
cir.ry(0, param=list_theta[i])
# input the unitary
if len(U.shape) == 1:
cir.rz(0, param=U)
elif i % 2 == 0:
cir.control_oracle(U, all_register, latex_name=r"$U$")
else:
cir.x(0)
cir.control_oracle(dagger(U), all_register, latex_name=r"$U^\dagger$")
cir.x(0)
cir.rz(0, param=list_phi[-1])
cir.ry(0, param=list_theta[-1])
return cir
def simulation_cir(
fn: Callable[[np.ndarray], np.ndarray],
U: Union[np.ndarray, paddle.Tensor, float],
approx: Optional[bool] = True,
deg: Optional[int] = 50,
length: Optional[float] = np.pi,
step_size: Optional[float] = 0.00001 * np.pi,
tol: Optional[float] = 1e-30,
) -> Circuit:
r"""Return a QPP circuit approximating `fn`.
Args:
fn: function to be approximated.
U: unitary input.
deg: degree of approximation, defaults to be :math:`50`.
approx: whether approximately find the angle of circuits, defaults to be ``True``
length: half of approximation width, defaults to be :math:`\pi`.
step_size: sampling frequency of data points, defaults to be :math:`0.00001 \pi`.
tol: error tolerance, defaults to be :math:`10^{-30}`.
Returns:
a QPP circuit approximating `fn` in Theorem 6 of paper https://arxiv.org/abs/2209.14278.
"""
f = laurent_generator(fn, step_size, deg, length)
if np.abs(f.max_norm - 1) < 1e-2:
f = f * (0.999999999 / f.max_norm)
P, Q = pair_generation(f)
revise_tol(tol)
list_theta, list_phi = (
qpp_angle_approximator(P, Q) if approx else qpp_angle_finder(P, Q)
)
return qpp_cir(list_theta, list_phi, U)
def qps(
U: Union[np.ndarray, paddle.Tensor],
initial_state: Union[np.ndarray, paddle.Tensor, State],
) -> Tuple[float, State]:
r"""Algorithm for quantum phase search, see Algorithm 1 and 2 of paper https://arxiv.org/abs/2209.14278.
Args:
U: target unitary
initial_state: the input quantum state
Returns:
contains the following two elements:
* an eigenphase of U
* its corresponding eigenstate that has overlap with the initial state
"""
assert is_unitary(U)
U, initial_state = _type_transform(U, "tensor"), _type_transform(
initial_state, "state_vector"
)
initial_state = zero_state(1).kron(initial_state)
def p_func(x):
target = 2.0 * np.arctan(np.sin(x) / 0.01) / np.pi
return np.sqrt((1 + target) / 2)
# function approximation
P = laurent_generator(p_func, 0.00001 * np.pi, 160, np.pi)
Q = Q_generation(P)
list_theta, list_phi = qpp_angle_approximator(P, Q)
# Algorithm setting
T, Q, Delta = 13, 10, 0.2
Delta_bar = Delta + np.pi / (2 ** (Q + 1))
op_M = Measure() # measurement
op_C = [
Collapse(0, desired_result="0"),
Collapse(0, desired_result="1"),
] # collapse operator
op_X = X(0) # x gate used to reset the first qubit
def __measure_and_collapse(target_state: State) -> Tuple[float, State]:
r"""Measure ``target_state``, and return the measurement outcome and collapsed state"""
prob_dist = op_M(target_state, 0).tolist()
result = np.random.choice(2, 1, p=prob_dist)[0]
return result, op_C[result](target_state)
def __interval_search(
U: paddle.Tensor, input_state: State, interval: Tuple[int, int]
) -> Tuple[int, int]:
r"""Find a smaller interval containing eigenphases from ``interval``, Algorithm 1."""
x_low, x_up = interval[0], interval[1]
theta = (x_up + x_low) / 2
state = input_state.clone()
# if the interval is too wide, the update rule is different
if x_up - x_low > 2 * np.pi - 2 * Delta:
state = qpp_cir(list_theta, list_phi, np.exp(-1j * theta).item() * U)(state)
result, state = __measure_and_collapse(state)
if result == 0:
x_low = -Delta
x_up += Delta
else:
x_low -= Delta
x_up = Delta
state = op_X(state)
theta = (x_up + x_low) / 2
# begin interval search formally
for _ in range(Q):
state = qpp_cir(list_theta, list_phi, np.exp(-1j * theta).item() * U)(state)
result, state = __measure_and_collapse(state)
# update the interval according to the measurement result of the first qubit
# - if the measurement result is 0, choose the RHS interval
# - otherwise, choose the LHS and reset the first qubit
if result == 0:
x_low = theta - Delta
else:
x_up = theta + Delta
state = op_X(state)
theta = (x_up + x_low) / 2
return [x_low, x_up], state
# Algorithm 2
phase_estimate = 0
p = int(np.round(1 / Delta_bar))
itr_interval = [-np.pi, np.pi]
itr_state = initial_state
for j in range(T):
itr_interval, itr_state = __interval_search(U, itr_state, itr_interval)
lamb = (itr_interval[0] + itr_interval[1]) / 2
phase_estimate += lamb * (Delta**j)
# update unitary and interval, so that we can start a new interval search
U = np.linalg.matrix_power(U, p) * np.exp(-1j * p * lamb)
itr_interval = ((np.array(itr_interval) - lamb) * p).tolist()
return phase_estimate, partial_trace(itr_state, 2**1, U.shape[0], A_or_B=1)
def qubitize(
block_enc: Union[np.ndarray, paddle.Tensor], num_block_qubits: int
) -> paddle.Tensor:
r"""Qubitize the block encoding to keep subspace invariant using one more ancilla qubit,
see paper http://arxiv.org/abs/1610.06546 for more details.
Args:
block_enc: the target block encoding.
num_block_qubits: number of ancilla qubits used in block encoding.
Returns:
the qubitized version for ``block_enc``.
"""
if isinstance(block_enc, np.ndarray):
block_enc = paddle.to_tensor(block_enc)
n = int(log2(block_enc.shape[0]))
cir = Circuit(n + 1)
cir.h(0)
cir.x(0)
cir.control_oracle(block_enc, list(range(n + 1)))
cir.x(0)
cir.control_oracle(dagger(block_enc), list(range(n + 1)))
cir.x(0)
cir.h(0)
num_qubits = n - num_block_qubits
zero_states = zero_state(num_block_qubits, backend=Backend.DensityMatrix)
zero_projector = paddle.kron(
2 * zero_states.data - paddle.eye(2**num_block_qubits),
paddle.eye(2**num_qubits),
)
return (cir.unitary_matrix() @ zero_projector).cast(block_enc.dtype)
def purification_block_enc(num_qubits: int, num_block_qubits: int) -> paddle.Tensor:
r"""Randomly generate a :math:`(n + m)`-qubit qubitized block encoding of a :math:`n`-qubit density matrix.
Args:
num_qubits: number of qubits :math:`n`.
num_block_qubits: number of ancilla qubits :math:`m > n` used in block encoding.
Returns:
a :math:`2^{n + m} \times 2^{n + m}` unitary matrix that its upper-left block is a density matrix.
"""
assert (
num_qubits < num_block_qubits
), f"the number of block qubits need to be larger than the number of qubits {num_qubits}: received {num_block_qubits}"
V = unitary_random(num_block_qubits)
aux1_reg = list(range(num_block_qubits - num_qubits))
aux2_reg = list(range(num_block_qubits - num_qubits, num_block_qubits))
sys_reg = list(range(num_block_qubits, num_qubits + num_block_qubits))
cir = Circuit(num_qubits + num_block_qubits)
cir.oracle(V, aux1_reg + aux2_reg)
for i in range(num_qubits):
cir.swap([aux2_reg[i], sys_reg[i]])
cir.oracle(dagger(V), aux1_reg + aux2_reg)
zero_states = paddle.eye(2**num_block_qubits, 1)
reflector = 2 * zero_states @ dagger(zero_states) - paddle.eye(
2**num_block_qubits
)
cir.oracle(reflector, list(range(num_block_qubits)))
return cir.unitary_matrix()
...@@ -17,7 +17,8 @@ r""" ...@@ -17,7 +17,8 @@ r"""
The module of the quantum state. The module of the quantum state.
""" """
from .state import State from .state import State, is_state_vector, is_density_matrix
from .state import _type_fetch, _type_transform
from .common import zero_state from .common import zero_state
from .common import computational_basis from .common import computational_basis
from .common import bell_state from .common import bell_state
......
...@@ -21,15 +21,16 @@ import collections ...@@ -21,15 +21,16 @@ import collections
import math import math
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
from scipy.linalg import expm
import random import random
import paddle import paddle
import QCompute import QCompute
import paddle_quantum import paddle_quantum
from ..backend import Backend from ..backend import Backend
from ..backend import state_vector, density_matrix, quleaf from ..backend import state_vector, density_matrix, quleaf
from ..hamiltonian import Hamiltonian from ..hamiltonian import Hamiltonian
from typing import Optional, Union, Iterable from typing import Optional, Union, Iterable, Tuple
class State(object): class State(object):
r"""The quantum state class. r"""The quantum state class.
...@@ -39,99 +40,215 @@ class State(object): ...@@ -39,99 +40,215 @@ class State(object):
num_qubits: The number of qubits contained in the quantum state. Defaults to ``None``, which means it will be inferred by the data. num_qubits: The number of qubits contained in the quantum state. Defaults to ``None``, which means it will be inferred by the data.
backend: Used to specify the backend used. Defaults to ``None``, which means to use the default backend. backend: Used to specify the backend used. Defaults to ``None``, which means to use the default backend.
dtype: Used to specify the data dtype of the data. Defaults to ``None``, which means to use the default data type. dtype: Used to specify the data dtype of the data. Defaults to ``None``, which means to use the default data type.
override: whether override the input test. ONLY for internal use. Defaults to ``False``.
Raises: Raises:
Exception: The shape of the data is not correct. ValueError: Cannot recognize the backend.
NotImplementedError: If the backend is wrong or not implemented.
""" """
def __init__( def __init__(
self, data: Union[paddle.Tensor, np.ndarray, QCompute.QEnv], num_qubits: Optional[int] = None, self, data: Union[paddle.Tensor, np.ndarray, QCompute.QEnv], num_qubits: Optional[int] = None,
backend: Optional[paddle_quantum.Backend] = None, dtype: Optional[str] = None backend: Optional[paddle_quantum.Backend] = None, dtype: Optional[str] = None, override: Optional[bool] = False
): ):
# TODO: need to check whether it is a legal quantum state # TODO: need to check whether it is a legal quantum state
super().__init__() super().__init__()
self.backend = paddle_quantum.get_backend() if backend is None else backend self.backend = paddle_quantum.get_backend() if backend is None else backend
if self.backend != Backend.QuLeaf:
if not isinstance(data, paddle.Tensor):
data = paddle.to_tensor(data)
if dtype is not None:
data = paddle.cast(data, dtype)
self.dtype = dtype if dtype is not None else paddle_quantum.get_dtype() self.dtype = dtype if dtype is not None else paddle_quantum.get_dtype()
if self.backend != Backend.QuLeaf and not isinstance(data, paddle.Tensor):
data = paddle.to_tensor(data, dtype=self.dtype)
self.data = data
# data input test
if self.backend == Backend.StateVector: if self.backend == Backend.StateVector:
if data.shape[-1] == 1: if data.shape[-1] == 1:
data = paddle.squeeze(data) self.data = paddle.squeeze(data)
self.data = data
if num_qubits is not None: if not override:
if data.shape[-1] != 2 ** num_qubits: is_vec, data_message = is_state_vector(data)
raise Exception("The shape of the data should (2 ** num_qubits, ).") assert is_vec, \
else: f"The input data is not a legal state vector: error message {data_message}"
num_qubits = int(math.log2(data.shape[-1])) assert num_qubits is None or num_qubits == data_message, \
assert 2 ** num_qubits == data.shape[-1], "The length of the state should be the integer power of two." f"num_qubits does not agree: expected {num_qubits}, received {data_message}"
num_qubits = data_message
elif self.backend == Backend.DensityMatrix: elif self.backend == Backend.DensityMatrix:
self.data = data
if num_qubits is not None: if not override:
if data.shape[-1] != 2 ** num_qubits or data.shape[-2] != 2 ** num_qubits: is_den, data_message = is_density_matrix(data)
raise Exception("The shape of the data should (2 ** num_qubits, 2 ** num_qubits).") assert is_den, \
else: f"The input data is not a legal density matrix: error message {data_message}"
assert data.shape[-1] == data.shape[-2], "The data should be a square matrix." assert num_qubits is None or num_qubits == data_message, \
num_qubits = int(math.log2(data.shape[-1])) f"num_qubits does not agree: expected {num_qubits}, received {data_message}"
assert 2 ** num_qubits == data.shape[-1], "The data should be integer power of 2." num_qubits = data_message
elif self.backend == Backend.UnitaryMatrix: elif self.backend == Backend.UnitaryMatrix:
self.data = data # no need to check the state in unitary backend
if num_qubits is not None: if num_qubits is None:
if data.shape[-1] != 2 ** num_qubits or data.shape[-2] != 2 ** num_qubits: num_qubits = int(math.log2(data.shape[0]))
raise Exception("The shape of the data should (2 ** num_qubits, 2 ** num_qubits).")
else:
assert data.shape[-1] == data.shape[-2], "The data should be a square matrix."
num_qubits = int(math.log2(data.shape[-1]))
assert 2 ** num_qubits == data.shape[-1], "The data should be integer power of 2."
elif self.backend == Backend.QuLeaf: elif self.backend == Backend.QuLeaf:
self.data = data
if quleaf.get_quleaf_backend() != QCompute.BackendName.LocalBaiduSim2: if quleaf.get_quleaf_backend() != QCompute.BackendName.LocalBaiduSim2:
assert quleaf.get_quleaf_token() is not None, "You must input token tu use cloud server." assert quleaf.get_quleaf_token() is not None, "You must input token tu use cloud server."
self.gate_history = [] self.gate_history = []
self.param_list = [] self.param_list = []
self.num_param = 0 self.num_param = 0
else: else:
raise NotImplementedError raise ValueError(
f"Cannot recognize the backend {self.backend}")
self.num_qubits = num_qubits self.num_qubits = num_qubits
@property @property
def ket(self) -> paddle.Tensor: def ket(self) -> paddle.Tensor:
r""" return the ket form in state_vector backend r"""Return the ket form in state_vector backend
Raises:
ValueError: the backend must be in StateVector
Returns: Returns:
ket form of the state ket form of the state
""" """
if self.backend != Backend.StateVector: if self.backend != Backend.StateVector:
raise Exception("the backend must be in state_vector to raise the ket form of state") raise ValueError(
f"the backend must be in StateVector: received {self.backend}")
return self.data.reshape([2 ** self.num_qubits, 1]) return self.data.reshape([2 ** self.num_qubits, 1])
@property @property
def bra(self) -> paddle.Tensor: def bra(self) -> paddle.Tensor:
r""" return the bra form in state_vector backend r"""Return the bra form in state_vector backend
Raises:
ValueError: the backend must be in StateVector
Returns: Returns:
bra form of the state bra form of the state
""" """
if self.backend != Backend.StateVector: if self.backend != Backend.StateVector:
raise Exception("the backend must be in state_vector to raise the bra form of state") raise ValueError(
f"the backend must be in StateVector: received {self.backend}")
return paddle.conj(self.data.reshape([1, 2 ** self.num_qubits])) return paddle.conj(self.data.reshape([1, 2 ** self.num_qubits]))
def normalize(self) -> None:
r"""Normalize this state
Raises:
NotImplementedError: does not support normalization for the backend
"""
data = self.data
if self.backend == Backend.StateVector:
data /= paddle.norm(paddle.abs(data))
elif self.backend == Backend.DensityMatrix:
data /= paddle.trace(data)
else:
raise NotImplementedError(
f"Does not support normalization for the backend {self.backend}")
self.data = data
def evolve(self, H: Union[np.ndarray, paddle.Tensor, Hamiltonian], t: float) -> None:
r"""Evolve the state under the Hamiltonian `H` i.e. apply unitary operator :math:`e^{-itH}`
Args:
H: the Hamiltonian of the system
t: the evolution time
Raises:
NotImplementedError: does not support evolution for the backend
"""
if isinstance(H, Hamiltonian):
num_qubits = max(self.num_qubits, H.n_qubits)
H = H.construct_h_matrix(qubit_num=num_qubits)
else:
num_qubits = int(math.log2(H.shape[0]))
H = _type_transform(H, "numpy")
assert num_qubits == self.num_qubits, \
f"the # of qubits of Hamiltonian and this state are not the same: received {num_qubits}, expect {self.num_qubits}"
e_itH = paddle.to_tensor(expm(-1j * t * H), dtype=self.dtype)
if self.backend == Backend.StateVector:
self.data = paddle.squeeze(e_itH @ self.ket)
elif self.backend == Backend.DensityMatrix:
self.data = e_itH @ self.data @ paddle.conj(e_itH.T)
else:
raise NotImplementedError(
f"Does not support evolution for the backend {self.backend}")
def kron(self, other: 'State') -> 'State':
r"""Kronecker product between states
Args:
other: a quantum state
Returns:
the tensor product of these two states
"""
backend, dtype = self.backend, self.dtype
assert backend == other.backend, \
f"backends between two States are not the same: received {backend} and {other.backend}"
assert dtype == other.dtype, \
f"dtype between two States are not the same: received {dtype} and {other.dtype}"
if backend == Backend.StateVector:
return State(paddle.kron(self.ket, other.ket), backend=backend)
else:
return State(paddle.kron(self.data, other.data), backend=backend, dtype=dtype)
def __matmul__(self, other: 'State') -> paddle.Tensor:
r"""Matrix product between states or between tensor and state
Args:
other: a quantum state
Raises:
NotImplementedError: does not support the product between State and input format.
ValueError: Cannot multiply two state vectors: check the backend
Returns:
the product of these two states
"""
if not isinstance(other, State):
raise NotImplementedError(
f"does not support the product between State and {type(other)}")
if self.backend == Backend.StateVector:
raise ValueError(
"Cannot multiply two state vectors: check the backend")
return self.data @ other.data
def __rmatmul__(self, other: 'State') -> paddle.Tensor:
r"""Matrix product between states or between state and tensor
Args:
other: a quantum state
Raises:
NotImplementedError: does not support the product between State and input format.
ValueError: Cannot multiply two state vectors: check the backend
Returns:
the product of these two states
"""
if not isinstance(other, State):
raise NotImplementedError(
f"does not support the product between {type(other)} and State")
if self.backend == Backend.StateVector:
raise ValueError(
"Cannot multiply two state vectors: check your backend")
return other.data @ self.data
def numpy(self) -> np.ndarray: def numpy(self) -> np.ndarray:
r"""get the data in numpy. r"""Get the data in numpy.
Returns: Returns:
The numpy array of the data for the quantum state. The numpy array of the data for the quantum state.
""" """
return self.data.numpy() return self.data.numpy()
def to(self, backend: str, dtype: str, device: str, blocking: str): def to(self, backend: str, dtype: str = None, device: str = None, blocking: str = None) -> None:
r"""Change the property of the state. r"""Change the property of the state.
Args: Args:
...@@ -139,20 +256,31 @@ class State(object): ...@@ -139,20 +256,31 @@ class State(object):
dtype: Specify the new data type of the state. dtype: Specify the new data type of the state.
device: Specify the new device of the state. device: Specify the new device of the state.
blocking: Specify the new blocking of the state. blocking: Specify the new blocking of the state.
Raises
NotImplementedError: only support transformation between StateVector and DensityMatrix
NotImplementedError: Transformation for device or blocking is not supported.
Returns:
Return a error because this function is not implemented.
""" """
# TODO: to be implemented if (backend not in {"state_vector", "density_matrix"} or
return NotImplementedError self.backend not in {Backend.StateVector, Backend.DensityMatrix}):
raise NotImplementedError(
"Only support transformation between StateVector and DensityMatrix")
if device is not None or blocking is not None:
raise NotImplementedError(
"Transformation for device or blocking is not supported")
self.data = _type_transform(self, backend).data
self.dtype = self.dtype if dtype is None else dtype
def clone(self) -> 'paddle_quantum.State': def clone(self) -> 'State':
r"""Return a copy of the quantum state. r"""Return a copy of the quantum state.
Returns: Returns:
A new state which is identical to this state. A new state which is identical to this state.
""" """
return State(self.data, self.num_qubits, self.backend, self.dtype) return State(self.data, self.num_qubits, self.backend, self.dtype, override=True)
def __str__(self): def __str__(self):
return str(self.data.numpy()) return str(self.data.numpy())
...@@ -212,17 +340,15 @@ class State(object): ...@@ -212,17 +340,15 @@ class State(object):
prob_amplitude = paddle.multiply(paddle.conj(state_data), state_data).real() prob_amplitude = paddle.multiply(paddle.conj(state_data), state_data).real()
elif self.backend == paddle_quantum.Backend.DensityMatrix: elif self.backend == paddle_quantum.Backend.DensityMatrix:
prob_amplitude = paddle.zeros([2 ** self.num_qubits]) prob_amplitude = paddle.zeros([2 ** self.num_qubits])
for idx in range(0, 2 ** self.num_qubits): for idx in range(2 ** self.num_qubits):
prob_amplitude[idx] += state_data[idx, idx].real() prob_amplitude[idx] += state_data[idx, idx].real()
else: else:
raise NotImplementedError raise NotImplementedError
prob_amplitude = prob_amplitude.tolist() prob_amplitude = prob_amplitude.tolist()
all_case = [bin(element)[2:].zfill(self.num_qubits) for element in range(0, 2 ** self.num_qubits)] all_case = [bin(element)[2:].zfill(self.num_qubits) for element in range(2 ** self.num_qubits)]
samples = random.choices(all_case, weights=prob_amplitude, k=shots) samples = random.choices(all_case, weights=prob_amplitude, k=shots)
counter = collections.Counter(samples) counter = collections.Counter(samples)
measured_results = [] measured_results = [([key[idx] for idx in qubits_list], val) for key, val in counter.items()]
for key, val in counter.items():
measured_results.append(([key[idx] for idx in qubits_list], val))
temp_res = sum(((-1) ** key.count('1') * val / shots for key, val in measured_results)) temp_res = sum(((-1) ** key.count('1') * val / shots for key, val in measured_results))
result += coeff * temp_res result += coeff * temp_res
return result return result
...@@ -252,15 +378,11 @@ class State(object): ...@@ -252,15 +378,11 @@ class State(object):
QCompute.MeasureZ(*state_data.Q.toListPair()) QCompute.MeasureZ(*state_data.Q.toListPair())
result = state_data.commit(shots, fetchMeasure=True)['counts'] result = state_data.commit(shots, fetchMeasure=True)['counts']
result = {''.join(reversed(key)): value for key, value in result.items()} result = {''.join(reversed(key)): value for key, value in result.items()}
if qubits_idx is not None:
# new_result = [(self.__process_string(key, qubits_idx), value) for key, value in result.items()]
# result = self.__process_similiar(new_result)
pass
elif self.backend == paddle_quantum.Backend.StateVector: elif self.backend == paddle_quantum.Backend.StateVector:
prob_amplitude = paddle.multiply(paddle.conj(self.data), self.data).real() prob_amplitude = paddle.multiply(paddle.conj(self.data), self.data).real()
elif self.backend == paddle_quantum.Backend.DensityMatrix: elif self.backend == paddle_quantum.Backend.DensityMatrix:
prob_amplitude = paddle.zeros([2 ** self.num_qubits]) prob_amplitude = paddle.zeros([2 ** self.num_qubits])
for idx in range(0, 2 ** self.num_qubits): for idx in range(2 ** self.num_qubits):
prob_amplitude[idx] += self.data[idx, idx].real() prob_amplitude[idx] += self.data[idx, idx].real()
else: else:
raise NotImplementedError raise NotImplementedError
...@@ -268,17 +390,12 @@ class State(object): ...@@ -268,17 +390,12 @@ class State(object):
prob_array = prob_amplitude prob_array = prob_amplitude
num_measured = self.num_qubits num_measured = self.num_qubits
elif isinstance(qubits_idx, (Iterable, int)): elif isinstance(qubits_idx, (Iterable, int)):
if isinstance(qubits_idx, int): qubits_idx = [qubits_idx] if isinstance(qubits_idx, int) else list(qubits_idx)
qubits_idx = [qubits_idx]
else:
qubits_idx = list(qubits_idx)
num_measured = len(qubits_idx) num_measured = len(qubits_idx)
prob_array = paddle.zeros([2 ** num_measured]) prob_array = paddle.zeros([2 ** num_measured])
for idx in range(0, 2 ** self.num_qubits): for idx in range(2 ** self.num_qubits):
binary = bin(idx)[2:].zfill(self.num_qubits) binary = bin(idx)[2:].zfill(self.num_qubits)
target_qubits = '' target_qubits = ''.join(binary[qubit_idx] for qubit_idx in qubits_idx)
for qubit_idx in qubits_idx:
target_qubits += binary[qubit_idx]
prob_array[int(target_qubits, base=2)] += prob_amplitude[idx] prob_array[int(target_qubits, base=2)] += prob_amplitude[idx]
else: else:
raise NotImplementedError raise NotImplementedError
...@@ -294,10 +411,7 @@ class State(object): ...@@ -294,10 +411,7 @@ class State(object):
result = {bin(idx)[2:].zfill(num_measured): val for idx, val in enumerate(freq)} result = {bin(idx)[2:].zfill(num_measured): val for idx, val in enumerate(freq)}
if plot: if plot:
assert num_measured < 6, "Too many qubits to plot" assert num_measured < 6, "Too many qubits to plot"
if shots == 0: ylabel = "Measured Probabilities" if shots == 0 else "Probabilities"
ylabel = "Measured Probabilities"
else:
ylabel = "Probabilities"
state_list = [bin(idx)[2:].zfill(num_measured) for idx in range(0, 2 ** num_measured)] state_list = [bin(idx)[2:].zfill(num_measured) for idx in range(0, 2 ** num_measured)]
plt.bar(range(2 ** num_measured), freq, tick_label=state_list) plt.bar(range(2 ** num_measured), freq, tick_label=state_list)
plt.xticks(rotation=90) plt.xticks(rotation=90)
...@@ -305,3 +419,213 @@ class State(object): ...@@ -305,3 +419,213 @@ class State(object):
plt.ylabel(ylabel) plt.ylabel(ylabel)
plt.show() plt.show()
return result return result
def _type_fetch(data: Union[np.ndarray, paddle.Tensor, State]) -> str:
r""" fetch the type of ``data``
Args:
data: the input data, and datatype of which should be either ``numpy.ndarray``,
''paddle.Tensor'' or ``paddle_quantum.State``
Returns:
string of datatype of ``data``, can be either ``"numpy"``, ``"tensor"``,
``"state_vector"`` or ``"density_matrix"``
Raises:
ValueError: does not support the current backend of input state.
TypeError: cannot recognize the current type of input data.
"""
if isinstance(data, np.ndarray):
return "numpy"
if isinstance(data, paddle.Tensor):
return "tensor"
if isinstance(data, State):
if data.backend == Backend.StateVector:
return "state_vector"
if data.backend == Backend.DensityMatrix:
return "density_matrix"
raise ValueError(
f"does not support the current backend {data.backend} of input state.")
raise TypeError(
f"cannot recognize the current type {type(data)} of input data.")
def _density_to_vector(rho: Union[np.ndarray, paddle.Tensor]) -> Union[np.ndarray, paddle.Tensor]:
r""" transform a density matrix to a state vector
Args:
rho: a density matrix (pure state)
Returns:
a state vector
Raises:
ValueError: the output state may not be a pure state
"""
type_str = _type_fetch(rho)
rho = paddle.to_tensor(rho)
eigval, eigvec = paddle.linalg.eigh(rho)
max_eigval = paddle.max(eigval).item()
err = np.abs(max_eigval - 1)
if err > 1e-6:
raise ValueError(
f"the output state may not be a pure state, maximum distance: {err}")
state = eigvec[:, paddle.argmax(eigval)]
return state.numpy() if type_str == "numpy" else state
def _type_transform(data: Union[np.ndarray, paddle.Tensor, State],
output_type: str) -> Union[np.ndarray, paddle.Tensor, State]:
r""" transform the datatype of ``input`` to ``output_type``
Args:
data: data to be transformed
output_type: datatype of the output data, type is either ``"numpy"``, ``"tensor"``,
``"state_vector"`` or ``"density_matrix"``
Returns:
the output data with expected type
Raises:
ValueError: does not support transformation to type.
"""
current_type = _type_fetch(data)
support_type = {"numpy", "tensor", "state_vector", "density_matrix"}
if output_type not in support_type:
raise ValueError(
f"does not support transformation to type {output_type}")
if current_type == output_type:
return data
if current_type == "numpy":
if output_type == "tensor":
return paddle.to_tensor(data)
data = np.squeeze(data)
# state_vector case
if output_type == "state_vector":
if len(data.shape) == 2:
data = _density_to_vector(data)
return State(data, backend=Backend.StateVector)
# density_matrix case
if len(data.shape) == 1:
data = data.reshape([len(data), 1])
data = data @ np.conj(data.T)
return State(data, backend=Backend.DensityMatrix)
if current_type == "tensor":
if output_type == "numpy":
return data.numpy()
data = paddle.squeeze(data)
# state_vector case
if output_type == "state_vector":
if len(data.shape) == 2:
data = _density_to_vector(data)
return State(data, backend=Backend.StateVector)
# density_matrix case
if len(data.shape) == 1:
data = data.reshape([len(data), 1])
data = data @ paddle.conj(data.T)
return State(data, backend=Backend.DensityMatrix)
if current_type == "state_vector":
if output_type == "density_matrix":
return State(data.ket @ data.bra, backend=Backend.DensityMatrix, num_qubits=data.num_qubits, override=True)
return data.ket.numpy() if output_type == "numpy" else data.ket
# density_matrix data
if output_type == "state_vector":
return State(_density_to_vector(data.data), backend=Backend.StateVector, num_qubits=data.num_qubits, override=True)
return data.numpy() if output_type == "numpy" else data.data
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
Notes:
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.
Notes:
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
min_eigval = paddle.min(paddle.linalg.eigvalsh(rho))
return {False, -1} if paddle.abs(min_eigval) > eps else {True, num_qubits}
...@@ -29,6 +29,7 @@ from typing import Optional, Union, Tuple, List ...@@ -29,6 +29,7 @@ from typing import Optional, Union, Tuple, List
import os import os
def plot_state_in_bloch_sphere( def plot_state_in_bloch_sphere(
state: List[paddle_quantum.State], state: List[paddle_quantum.State],
show_arrow: Optional[bool] = False, show_arrow: Optional[bool] = False,
...@@ -832,3 +833,4 @@ def __input_args_dtype_check(show_arrow: bool, save_gif: bool, filename: str, vi ...@@ -832,3 +833,4 @@ def __input_args_dtype_check(show_arrow: bool, save_gif: bool, filename: str, vi
), 'the type of "view_angle[0]" and "view_angle[1]" should be "int".' ), 'the type of "view_angle[0]" and "view_angle[1]" should be "int".'
if view_dist is not None: if view_dist is not None:
assert type(view_dist) == int, 'the type of "view_dist" should be "int".' assert type(view_dist) == int, 'the type of "view_dist" should be "int".'
paddlepaddle<=2.3.0 paddlepaddle>=2.2.0, <=2.3.0
scipy>=1.8.1 protobuf>=3.1.0, <=3.20.1
protobuf<=3.20.1
networkx>=2.5
qcompute qcompute
matplotlib networkx>=2.5
matplotlib>=3.3.0
scipy
tqdm tqdm
openfermion openfermion
pyscf; platform_system == "Linux" or platform_system == "Darwin" pyscf; platform_system == "Linux" or platform_system == "Darwin"
scikit-learn scikit-learn
opencv-python opencv-python
cvxpy
rich
...@@ -24,7 +24,7 @@ with open("README.md", "r", encoding="utf-8") as fh: ...@@ -24,7 +24,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
setuptools.setup( setuptools.setup(
name='paddle-quantum', name='paddle-quantum',
version='2.2.1', version='2.2.2',
author='Institute for Quantum Computing, Baidu INC.', author='Institute for Quantum Computing, Baidu INC.',
author_email='qml@baidu.com', author_email='qml@baidu.com',
description='Paddle Quantum is a quantum machine learning (QML) toolkit developed based on Baidu PaddlePaddle.', description='Paddle Quantum is a quantum machine learning (QML) toolkit developed based on Baidu PaddlePaddle.',
...@@ -44,7 +44,9 @@ setuptools.setup( ...@@ -44,7 +44,9 @@ setuptools.setup(
'paddle_quantum.operator', 'paddle_quantum.operator',
'paddle_quantum.state', 'paddle_quantum.state',
'paddle_quantum.qchem', 'paddle_quantum.qchem',
'paddle_quantum.qml',
'paddle_quantum.qsvt', 'paddle_quantum.qsvt',
'paddle_quantum.qpp',
'paddle_quantum.mbqc', 'paddle_quantum.mbqc',
'paddle_quantum.GIBBS', 'paddle_quantum.GIBBS',
'paddle_quantum.GIBBS.example', 'paddle_quantum.GIBBS.example',
...@@ -79,18 +81,20 @@ setuptools.setup( ...@@ -79,18 +81,20 @@ setuptools.setup(
'paddle_quantum.mbqc.VQSVD.example': ['*.txt'], 'paddle_quantum.mbqc.VQSVD.example': ['*.txt'],
}, },
install_requires=[ install_requires=[
'paddlepaddle<=2.3.0', 'paddlepaddle>=2.2.0, <=2.3.0',
'scipy>=1.8.1', 'protobuf>=3.1.0, <=3.20.1',
'protobuf<=3.20.1',
'networkx>=2.5',
'qcompute', 'qcompute',
'networkx>=2.5',
'matplotlib>=3.3.0', 'matplotlib>=3.3.0',
'scipy',
'tqdm', 'tqdm',
'openfermion', 'openfermion',
'pyscf; platform_system == "Linux" or platform_system == "Darwin"', 'pyscf; platform_system == "Linux" or platform_system == "Darwin"',
'opencv-python', 'opencv-python',
'scikit-learn', 'scikit-learn',
'fastdtw', 'fastdtw',
'cvxpy',
'rich',
], ],
python_requires='>=3.6, <4', python_requires='>=3.6, <4',
classifiers=[ classifiers=[
......
...@@ -257,7 +257,7 @@ ...@@ -257,7 +257,7 @@
"可以看到,在这个例子中,哈密顿量 $H_D$ 为\n", "可以看到,在这个例子中,哈密顿量 $H_D$ 为\n",
"\n", "\n",
"$$\n", "$$\n",
"H_D = -Z_0Z_1 - Z_1Z_2 - Z_2Z_3 - Z_3Z_0.\n", "H_D = -Z_0Z_1 - Z_1Z_2 - Z_2Z_3 - Z_3Z_0 - Z_1Z_3.\n",
"\\tag{12}\n", "\\tag{12}\n",
"$$\n", "$$\n",
"\n", "\n",
......
...@@ -261,7 +261,7 @@ ...@@ -261,7 +261,7 @@
"As you can see, in this example, the Hamiltonian $H_D$ is\n", "As you can see, in this example, the Hamiltonian $H_D$ is\n",
"\n", "\n",
"$$\n", "$$\n",
"H_D = -Z_0Z_1-Z_1Z_2-Z_2Z_3-Z_3Z_0.\n", "H_D = -Z_0Z_1-Z_1Z_2-Z_2Z_3-Z_3Z_0-Z_1Z_3.\n",
"\\tag{12}\n", "\\tag{12}\n",
"$$\n", "$$\n",
"\n", "\n",
......
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 1,
"id": "bearing-millennium", "id": "bearing-millennium",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
...@@ -153,7 +153,7 @@ ...@@ -153,7 +153,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 2,
"id": "unique-class", "id": "unique-class",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
...@@ -180,7 +180,7 @@ ...@@ -180,7 +180,7 @@
"from paddle_quantum.qinfo import negativity, logarithmic_negativity, partial_transpose, is_ppt\n", "from paddle_quantum.qinfo import negativity, logarithmic_negativity, partial_transpose, is_ppt\n",
"\n", "\n",
"input_state = bell_state(2)\n", "input_state = bell_state(2)\n",
"transposed_state = partial_transpose(input_state, 2) # 2 代表量子比特数量\n", "transposed_state = partial_transpose(input_state, 1) # 1 代表 A 系统量子比特数量\n",
"print(\"部分转置后的量子态为\\n\", transposed_state.numpy())\n", "print(\"部分转置后的量子态为\\n\", transposed_state.numpy())\n",
"\n", "\n",
"neg = negativity(input_state)\n", "neg = negativity(input_state)\n",
...@@ -211,6 +211,31 @@ ...@@ -211,6 +211,31 @@
"**注释:** 这两个保真度的公式的关系为 $F=F_s^2$ 。" "**注释:** 这两个保真度的公式的关系为 $F=F_s^2$ 。"
] ]
}, },
{
"cell_type": "code",
"execution_count": 3,
"id": "ccfef0e1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Tensor(shape=[4, 4], dtype=complex64, place=Place(cpu), stop_gradient=True,\n",
" [[(0.5+0j), 0j , 0j , (0.5+0j)],\n",
" [0j , 0j , 0j , 0j ],\n",
" [0j , 0j , 0j , 0j ],\n",
" [(0.5+0j), 0j , 0j , (0.5+0j)]])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"input_state.data"
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 4,
...@@ -402,7 +427,7 @@ ...@@ -402,7 +427,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 8,
"id": "motivated-association", "id": "motivated-association",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
...@@ -461,7 +486,7 @@ ...@@ -461,7 +486,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": 10,
"id": "enormous-workplace", "id": "enormous-workplace",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
...@@ -566,7 +591,7 @@ ...@@ -566,7 +591,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3 (ipykernel)", "display_name": "Python 3.8.13 ('pq')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -594,6 +619,11 @@ ...@@ -594,6 +619,11 @@
"toc_position": {}, "toc_position": {},
"toc_section_display": true, "toc_section_display": true,
"toc_window_display": false "toc_window_display": false
},
"vscode": {
"interpreter": {
"hash": "08942b1340a5932ff3a93f52933a99b0e263568f3aace1d262ffa4d9a0f2da31"
}
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 1,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-02-23T09:10:37.924098Z", "end_time": "2021-02-23T09:10:37.924098Z",
...@@ -147,7 +147,7 @@ ...@@ -147,7 +147,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 2,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-02-23T09:10:43.024928Z", "end_time": "2021-02-23T09:10:43.024928Z",
...@@ -173,7 +173,7 @@ ...@@ -173,7 +173,7 @@
"from paddle_quantum.qinfo import negativity, logarithmic_negativity, partial_transpose, is_ppt\n", "from paddle_quantum.qinfo import negativity, logarithmic_negativity, partial_transpose, is_ppt\n",
"\n", "\n",
"input_state = bell_state(2)\n", "input_state = bell_state(2)\n",
"transposed_state = partial_transpose(input_state, 2) # 2 stands for qubit numbers\n", "transposed_state = partial_transpose(input_state, 1) # 1 stands for the qubit number of system A\n",
"print(\"The transposed state is\\n\", transposed_state.numpy())\n", "print(\"The transposed state is\\n\", transposed_state.numpy())\n",
"\n", "\n",
"neg = negativity(input_state)\n", "neg = negativity(input_state)\n",
...@@ -205,7 +205,7 @@ ...@@ -205,7 +205,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 3,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-02-23T09:10:45.384306Z", "end_time": "2021-02-23T09:10:45.384306Z",
...@@ -294,7 +294,7 @@ ...@@ -294,7 +294,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 4,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-02-23T09:10:48.714566Z", "end_time": "2021-02-23T09:10:48.714566Z",
...@@ -320,7 +320,7 @@ ...@@ -320,7 +320,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 5,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-02-23T09:10:50.880991Z", "end_time": "2021-02-23T09:10:50.880991Z",
...@@ -354,7 +354,7 @@ ...@@ -354,7 +354,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 6,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-02-23T09:10:52.180240Z", "end_time": "2021-02-23T09:10:52.180240Z",
...@@ -391,7 +391,7 @@ ...@@ -391,7 +391,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 7,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-02-23T09:10:53.985975Z", "end_time": "2021-02-23T09:10:53.985975Z",
...@@ -450,7 +450,7 @@ ...@@ -450,7 +450,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 9,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-02-23T09:10:58.444882Z", "end_time": "2021-02-23T09:10:58.444882Z",
...@@ -551,7 +551,7 @@ ...@@ -551,7 +551,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3 (ipykernel)", "display_name": "Python 3.8.13 ('pq')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -579,6 +579,11 @@ ...@@ -579,6 +579,11 @@
"toc_position": {}, "toc_position": {},
"toc_section_display": true, "toc_section_display": true,
"toc_window_display": false "toc_window_display": false
},
"vscode": {
"interpreter": {
"hash": "08942b1340a5932ff3a93f52933a99b0e263568f3aace1d262ffa4d9a0f2da31"
}
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -111,7 +111,7 @@ ...@@ -111,7 +111,7 @@
"# 随机采样获取关于目标函数的数据点\n", "# 随机采样获取关于目标函数的数据点\n",
"def get_data():\n", "def get_data():\n",
" x_plot = np.arange(0, np.pi, np.pi/1000)\n", " x_plot = np.arange(0, np.pi, np.pi/1000)\n",
" y_plot = np.sin(5*x_plot) / (5*x_plot)\n", " y_plot = target_func(x_plot)\n",
" \n", " \n",
" np.random.seed(0)\n", " np.random.seed(0)\n",
" x_all = np.random.uniform(0, np.pi, 300)\n", " x_all = np.random.uniform(0, np.pi, 300)\n",
...@@ -587,7 +587,7 @@ ...@@ -587,7 +587,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3.8.13 ('newpq')", "display_name": "Python 3.8.13 ('pq')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -605,7 +605,7 @@ ...@@ -605,7 +605,7 @@
}, },
"vscode": { "vscode": {
"interpreter": { "interpreter": {
"hash": "de7cd855378eaab3f20deac830fabedba9ee866c40f7d3ccd0ebe0daf90b4a28" "hash": "08942b1340a5932ff3a93f52933a99b0e263568f3aace1d262ffa4d9a0f2da31"
} }
} }
}, },
......
...@@ -119,7 +119,7 @@ ...@@ -119,7 +119,7 @@
"# Randomly sample data points from the target function.\n", "# Randomly sample data points from the target function.\n",
"def get_data():\n", "def get_data():\n",
" x_plot = np.arange(0, np.pi, np.pi/1000)\n", " x_plot = np.arange(0, np.pi, np.pi/1000)\n",
" y_plot = np.sin(5*x_plot) / (5*x_plot)\n", " y_plot = target_func(x_plot)\n",
" \n", " \n",
" np.random.seed(0)\n", " np.random.seed(0)\n",
" x_all = np.random.uniform(0, np.pi, 300)\n", " x_all = np.random.uniform(0, np.pi, 300)\n",
...@@ -606,7 +606,7 @@ ...@@ -606,7 +606,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3.8.13 ('newpq')", "display_name": "Python 3.8.13 ('pq')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -624,7 +624,7 @@ ...@@ -624,7 +624,7 @@
}, },
"vscode": { "vscode": {
"interpreter": { "interpreter": {
"hash": "de7cd855378eaab3f20deac830fabedba9ee866c40f7d3ccd0ebe0daf90b4a28" "hash": "08942b1340a5932ff3a93f52933a99b0e263568f3aace1d262ffa4d9a0f2da31"
} }
} }
}, },
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 2,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:58:08.278605Z", "end_time": "2021-04-30T08:58:08.278605Z",
...@@ -85,12 +85,11 @@ ...@@ -85,12 +85,11 @@
"source": [ "source": [
"import numpy as np\n", "import numpy as np\n",
"import paddle\n", "import paddle\n",
"from paddle import matmul, trace, kron, real\n",
"import paddle_quantum as pq\n", "import paddle_quantum as pq\n",
"from paddle_quantum.ansatz.circuit import Circuit\n", "from paddle_quantum.ansatz.circuit import Circuit\n",
"from paddle_quantum.qinfo import state_fidelity, partial_trace\n", "from paddle_quantum.qinfo import state_fidelity, partial_trace\n",
"from paddle_quantum.linalg import dagger, haar_unitary\n", "from paddle_quantum.linalg import dagger, haar_unitary\n",
"from paddle_quantum.state.common import to_state" "from paddle_quantum.state import State"
] ]
}, },
{ {
...@@ -104,7 +103,7 @@ ...@@ -104,7 +103,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 3,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:58:08.292440Z", "end_time": "2021-04-30T08:58:08.292440Z",
...@@ -117,14 +116,15 @@ ...@@ -117,14 +116,15 @@
"N_B = 1 # 系统 B 的量子比特数\n", "N_B = 1 # 系统 B 的量子比特数\n",
"N = N_A + N_B # 总的量子比特数\n", "N = N_A + N_B # 总的量子比特数\n",
"SEED = 15 # 设置随机数种子\n", "SEED = 15 # 设置随机数种子\n",
"complex_dtype = 'complex128'\n",
"paddle.seed(SEED)\n", "paddle.seed(SEED)\n",
"pq.set_dtype('complex64') # 设置数据类型\n", "pq.set_dtype(complex_dtype) # 设置数据类型\n",
"pq.set_backend('density_matrix')\n",
"\n", "\n",
"V = haar_unitary(N).numpy() # 随机生成一个酉矩阵\n", "V = haar_unitary(N).numpy() # 随机生成一个酉矩阵\n",
"D = np.diag([0.4, 0.2, 0.2, 0.1, 0.1, 0, 0, 0]) # 输入目标态 rho 的谱\n", "D = np.diag([0.4, 0.2, 0.2, 0.1, 0.1, 0, 0, 0]) # 输入目标态 rho 的谱\n",
"V_H = V.conj().T # 进行厄尔米特转置\n", "rho_in = State(V @ D @ dagger(V)) # 生成输入量子态 rho_in\n",
"rho_in = to_state((V @ D @ V_H).astype('complex64')) # 生成输入量子态 rho_in\n", "rho_C = State(np.diag([1, 0])) # 生成辅助量子系统 C"
"rho_C = to_state(np.diag([1,0]).astype('complex64')) # 生成辅助量子系统 C"
] ]
}, },
{ {
...@@ -139,7 +139,7 @@ ...@@ -139,7 +139,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 4,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:58:08.310082Z", "end_time": "2021-04-30T08:58:08.310082Z",
...@@ -168,9 +168,9 @@ ...@@ -168,9 +168,9 @@
"# 用 Circuit 类搭建编码器 Encoder E\n", "# 用 Circuit 类搭建编码器 Encoder E\n",
"cir_Encoder = Circuit(N)\n", "cir_Encoder = Circuit(N)\n",
"for _ in range(cir_depth):\n", "for _ in range(cir_depth):\n",
" cir_Encoder.ry('full')\n", " cir_Encoder.ry()\n",
" cir_Encoder.rz('full')\n", " cir_Encoder.rz()\n",
" cir_Encoder.cnot('cycle')\n", " cir_Encoder.cnot()\n",
"print(\"初始化的电路:\") \n", "print(\"初始化的电路:\") \n",
"print(cir_Encoder)" "print(cir_Encoder)"
] ]
...@@ -193,7 +193,7 @@ ...@@ -193,7 +193,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 6,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:58:31.111139Z", "end_time": "2021-04-30T08:58:31.111139Z",
...@@ -206,23 +206,23 @@ ...@@ -206,23 +206,23 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"iter: 10 loss: 0.1594 fid: 0.8286\n", "iter: 10 loss: 0.1349 fid: 0.8595\n",
"iter: 20 loss: 0.1214 fid: 0.8741\n", "iter: 20 loss: 0.1084 fid: 0.8850\n",
"iter: 30 loss: 0.1078 fid: 0.8861\n", "iter: 30 loss: 0.1032 fid: 0.8913\n",
"iter: 40 loss: 0.1026 fid: 0.8906\n", "iter: 40 loss: 0.1009 fid: 0.8933\n",
"iter: 50 loss: 0.1017 fid: 0.8905\n", "iter: 50 loss: 0.1004 fid: 0.8940\n",
"iter: 60 loss: 0.1009 fid: 0.8925\n", "iter: 60 loss: 0.1002 fid: 0.8942\n",
"iter: 70 loss: 0.1005 fid: 0.8935\n", "iter: 70 loss: 0.1000 fid: 0.8944\n",
"iter: 80 loss: 0.1004 fid: 0.8935\n", "iter: 80 loss: 0.1000 fid: 0.8944\n",
"iter: 90 loss: 0.1002 fid: 0.8942\n", "iter: 90 loss: 0.1000 fid: 0.8945\n",
"iter: 100 loss: 0.1001 fid: 0.8949\n", "iter: 100 loss: 0.1000 fid: 0.8945\n",
"\n", "\n",
"训练后的电路:\n", "训练后的电路:\n",
"--Ry(3.020)----Rz(4.904)----*---------x----Ry(3.350)----Rz(4.894)----*---------x----Ry(6.877)----Rz(4.309)----*---------x----Ry(3.806)----Rz(1.201)----*---------x----Ry(4.422)----Rz(1.580)----*---------x----Ry(3.051)----Rz(-0.90)----*---------x--\n", "--Ry(3.862)----Rz(3.447)----*---------x----Ry(2.144)----Rz(3.484)----*---------x----Ry(4.603)----Rz(4.614)----*---------x----Ry(1.571)----Rz(2.048)----*---------x----Ry(5.139)----Rz(1.547)----*---------x----Ry(5.038)----Rz(0.564)----*---------x--\n",
" | | | | | | | | | | | | \n", " | | | | | | | | | | | | \n",
"--Ry(2.135)----Rz(3.829)----x----*----|----Ry(5.740)----Rz(3.472)----x----*----|----Ry(5.583)----Rz(4.673)----x----*----|----Ry(2.273)----Rz(2.103)----x----*----|----Ry(2.121)----Rz(0.393)----x----*----|----Ry(4.825)----Rz(5.194)----x----*----|--\n", "--Ry(2.371)----Rz(3.058)----x----*----|----Ry(6.245)----Rz(3.803)----x----*----|----Ry(5.527)----Rz(3.637)----x----*----|----Ry(2.757)----Rz(1.934)----x----*----|----Ry(-3.42)----Rz(1.019)----x----*----|----Ry(4.140)----Rz(5.329)----x----*----|--\n",
" | | | | | | | | | | | | \n", " | | | | | | | | | | | | \n",
"--Ry(2.911)----Rz(2.564)---------x----*----Ry(2.335)----Rz(1.657)---------x----*----Ry(6.486)----Rz(0.392)---------x----*----Ry(2.889)----Rz(1.981)---------x----*----Ry(0.753)----Rz(1.536)---------x----*----Ry(5.659)----Rz(3.284)---------x----*--\n", "--Ry(3.853)----Rz(3.867)---------x----*----Ry(1.823)----Rz(3.240)---------x----*----Ry(6.558)----Rz(0.734)---------x----*----Ry(2.828)----Rz(1.123)---------x----*----Ry(2.048)----Rz(-0.57)---------x----*----Ry(4.699)----Rz(1.856)---------x----*--\n",
" \n" " \n"
] ]
} }
...@@ -233,13 +233,13 @@ ...@@ -233,13 +233,13 @@
"ITR = 100 # 设置迭代次数\n", "ITR = 100 # 设置迭代次数\n",
"\n", "\n",
"class NET(paddle.nn.Layer):\n", "class NET(paddle.nn.Layer):\n",
" def __init__(self, cir, rho_in, rho_C, dtype='float32'):\n", " def __init__(self, cir, rho_in, rho_C):\n",
" super(NET, self).__init__()\n", " super(NET, self).__init__()\n",
" # 载入编码器 E\n", " # 载入编码器 E\n",
" self.cir = cir\n", " self.cir = cir\n",
" # 载入输入态 rho_in 和辅助态 rho_C\n", " # 载入输入态 rho_in 和辅助态 rho_C\n",
" self.rho_in = rho_in\n", " self.rho_in = rho_in.data\n",
" self.rho_C = rho_C\n", " self.rho_C = rho_C.data\n",
" # 设置可训练参数\n", " # 设置可训练参数\n",
" self.theta = cir.parameters()\n", " self.theta = cir.parameters()\n",
" \n", " \n",
...@@ -253,19 +253,19 @@ ...@@ -253,19 +253,19 @@
" D_dagger = E\n", " D_dagger = E\n",
"\n", "\n",
" # 编码量子态 rho_in\n", " # 编码量子态 rho_in\n",
" rho_BA = to_state(matmul(matmul(E, self.rho_in.data), E_dagger))\n", " rho_BA = E @ self.rho_in @ E_dagger\n",
" \n", " \n",
" # 取 partial_trace() 获得 rho_encode 与 rho_trash\n", " # 取 partial_trace() 获得 rho_encode 与 rho_trash\n",
" rho_encode = to_state(partial_trace(rho_BA, 2 ** N_B, 2 ** N_A, 1))\n", " rho_encode = partial_trace(rho_BA, 2 ** N_B, 2 ** N_A, 1)\n",
" rho_trash = to_state(partial_trace(rho_BA, 2 ** N_B, 2 ** N_A, 2))\n", " rho_trash = partial_trace(rho_BA, 2 ** N_B, 2 ** N_A, 2)\n",
"\n", "\n",
" # 解码得到量子态 rho_out\n", " # 解码得到量子态 rho_out\n",
" rho_CA = to_state(kron(self.rho_C.data, rho_encode.data))\n", " rho_CA = paddle.kron(self.rho_C, rho_encode)\n",
" rho_out = to_state(matmul(matmul(D, rho_CA.data), D_dagger))\n", " rho_out = D @ rho_CA @ D_dagger\n",
" \n", " \n",
" # 通过 rho_trash 计算损失函数\n", " # 通过 rho_trash 计算损失函数\n",
" zero_Hamiltonian = paddle.to_tensor(np.diag([1,0]).astype('complex64'))\n", " zero_Hamiltonian = paddle.to_tensor(np.diag([1, 0]).astype(complex_dtype))\n",
" loss = 1 - real(trace(matmul(zero_Hamiltonian, rho_trash.data)))\n", " loss = 1 - paddle.real(paddle.trace(zero_Hamiltonian @ rho_trash))\n",
"\n", "\n",
" return loss, rho_out\n", " return loss, rho_out\n",
"\n", "\n",
...@@ -322,7 +322,7 @@ ...@@ -322,7 +322,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3 (ipykernel)", "display_name": "Python 3.7.13 ('py3.7_pq2.2.1')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -336,7 +336,7 @@ ...@@ -336,7 +336,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.13" "version": "3.7.13"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
...@@ -350,6 +350,11 @@ ...@@ -350,6 +350,11 @@
"toc_position": {}, "toc_position": {},
"toc_section_display": true, "toc_section_display": true,
"toc_window_display": false "toc_window_display": false
},
"vscode": {
"interpreter": {
"hash": "4e4e2eb86ad73936e915e7c7629a18a8ca06348106cf3e66676b9578cb1a47dd"
}
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 2,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:57:29.266507Z", "end_time": "2021-04-30T08:57:29.266507Z",
...@@ -85,12 +85,11 @@ ...@@ -85,12 +85,11 @@
"source": [ "source": [
"import numpy as np\n", "import numpy as np\n",
"import paddle\n", "import paddle\n",
"from paddle import matmul, trace, kron, real\n",
"import paddle_quantum as pq\n", "import paddle_quantum as pq\n",
"from paddle_quantum.ansatz.circuit import Circuit\n", "from paddle_quantum.ansatz.circuit import Circuit\n",
"from paddle_quantum.qinfo import state_fidelity, partial_trace\n", "from paddle_quantum.qinfo import state_fidelity, partial_trace\n",
"from paddle_quantum.linalg import dagger, haar_unitary\n", "from paddle_quantum.linalg import dagger, haar_unitary\n",
"from paddle_quantum.state.common import to_state" "from paddle_quantum.state import State"
] ]
}, },
{ {
...@@ -105,7 +104,7 @@ ...@@ -105,7 +104,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 3,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:57:29.283394Z", "end_time": "2021-04-30T08:57:29.283394Z",
...@@ -118,14 +117,15 @@ ...@@ -118,14 +117,15 @@
"N_B = 1 # Number of qubits in system B\n", "N_B = 1 # Number of qubits in system B\n",
"N = N_A + N_B # Total number of qubits\n", "N = N_A + N_B # Total number of qubits\n",
"SEED = 15 # Set random seed\n", "SEED = 15 # Set random seed\n",
"complex_dtype = 'complex128'\n",
"paddle.seed(SEED)\n", "paddle.seed(SEED)\n",
"pq.set_dtype('complex64') # set data type\n", "pq.set_dtype(complex_dtype) # set data type\n",
"pq.set_backend('density_matrix')\n",
"\n", "\n",
"V = haar_unitary(N).numpy() # Generate a random unitary matrix\n", "V = haar_unitary(N).numpy() # Generate a random unitary matrix\n",
"D = np.diag([0.4, 0.2, 0.2, 0.1, 0.1, 0, 0, 0]) # Set the spectrum of the target state rho\n", "D = np.diag([0.4, 0.2, 0.2, 0.1, 0.1, 0, 0, 0]) # Set the spectrum of the target state rho\n",
"V_H = V.conj().T # Apply Hermitian transpose\n", "rho_in = State(V @ D @ dagger(V)) # Generate input state rho_in\n",
"rho_in = to_state((V @ D @ V_H).astype('complex64')) # Generate input state rho_in\n", "rho_C = State(np.diag([1, 0])) # Generate ancilla state rho_C"
"rho_C = to_state(np.diag([1,0]).astype('complex64')) # Generate ancilla state rho_C"
] ]
}, },
{ {
...@@ -139,7 +139,7 @@ ...@@ -139,7 +139,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 4,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:57:29.301622Z", "end_time": "2021-04-30T08:57:29.301622Z",
...@@ -193,7 +193,7 @@ ...@@ -193,7 +193,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 6,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:57:52.962725Z", "end_time": "2021-04-30T08:57:52.962725Z",
...@@ -205,23 +205,23 @@ ...@@ -205,23 +205,23 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"iter: 10 loss: 0.1700 fid: 0.8226\n", "iter: 10 loss: 0.1285 fid: 0.8609\n",
"iter: 20 loss: 0.1329 fid: 0.8605\n", "iter: 20 loss: 0.1090 fid: 0.8800\n",
"iter: 30 loss: 0.1138 fid: 0.8793\n", "iter: 30 loss: 0.1040 fid: 0.8877\n",
"iter: 40 loss: 0.1075 fid: 0.8856\n", "iter: 40 loss: 0.1017 fid: 0.8899\n",
"iter: 50 loss: 0.1024 fid: 0.8891\n", "iter: 50 loss: 0.1007 fid: 0.8913\n",
"iter: 60 loss: 0.1010 fid: 0.8917\n", "iter: 60 loss: 0.1002 fid: 0.8923\n",
"iter: 70 loss: 0.1003 fid: 0.8927\n", "iter: 70 loss: 0.1001 fid: 0.8925\n",
"iter: 80 loss: 0.1001 fid: 0.8932\n", "iter: 80 loss: 0.1000 fid: 0.8925\n",
"iter: 90 loss: 0.1000 fid: 0.8936\n", "iter: 90 loss: 0.1000 fid: 0.8925\n",
"iter: 100 loss: 0.1000 fid: 0.8939\n", "iter: 100 loss: 0.1000 fid: 0.8926\n",
"\n", "\n",
"The trained circuit:\n", "The trained circuit:\n",
"--Ry(3.715)----Rz(3.480)----*---------x----Ry(5.783)----Rz(3.863)----*---------x----Ry(4.643)----Rz(5.174)----*---------x----Ry(3.187)----Rz(1.627)----*---------x----Ry(5.195)----Rz(1.282)----*---------x----Ry(5.990)----Rz(0.919)----*---------x--\n", "--Ry(2.426)----Rz(3.029)----*---------x----Ry(4.490)----Rz(4.618)----*---------x----Ry(5.908)----Rz(4.413)----*---------x----Ry(1.273)----Rz(0.885)----*---------x----Ry(6.689)----Rz(1.169)----*---------x----Ry(5.038)----Rz(0.564)----*---------x--\n",
" | | | | | | | | | | | | \n", " | | | | | | | | | | | | \n",
"--Ry(2.889)----Rz(3.898)----x----*----|----Ry(7.024)----Rz(5.530)----x----*----|----Ry(4.595)----Rz(4.244)----x----*----|----Ry(3.576)----Rz(2.981)----x----*----|----Ry(-1.39)----Rz(0.356)----x----*----|----Ry(3.557)----Rz(4.126)----x----*----|--\n", "--Ry(1.004)----Rz(3.807)----x----*----|----Ry(7.110)----Rz(5.279)----x----*----|----Ry(5.825)----Rz(6.107)----x----*----|----Ry(2.676)----Rz(2.543)----x----*----|----Ry(-1.62)----Rz(-1.07)----x----*----|----Ry(5.135)----Rz(5.329)----x----*----|--\n",
" | | | | | | | | | | | | \n", " | | | | | | | | | | | | \n",
"--Ry(3.593)----Rz(3.298)---------x----*----Ry(0.958)----Rz(2.486)---------x----*----Ry(6.317)----Rz(0.314)---------x----*----Ry(3.837)----Rz(2.775)---------x----*----Ry(3.415)----Rz(-0.07)---------x----*----Ry(4.543)----Rz(3.359)---------x----*--\n", "--Ry(4.519)----Rz(1.909)---------x----*----Ry(3.341)----Rz(2.543)---------x----*----Ry(7.258)----Rz(-0.10)---------x----*----Ry(3.402)----Rz(2.748)---------x----*----Ry(3.975)----Rz(0.944)---------x----*----Ry(4.903)----Rz(1.856)---------x----*--\n",
" \n" " \n"
] ]
} }
...@@ -237,8 +237,8 @@ ...@@ -237,8 +237,8 @@
" # load the circuit of the encoder E\n", " # load the circuit of the encoder E\n",
" self.cir = cir\n", " self.cir = cir\n",
" # load the input state rho_in and the ancilla state rho_C\n", " # load the input state rho_in and the ancilla state rho_C\n",
" self.rho_in = rho_in\n", " self.rho_in = rho_in.data\n",
" self.rho_C = rho_C\n", " self.rho_C = rho_C.data\n",
" # set trainable parameters\n", " # set trainable parameters\n",
" self.theta = cir.parameters()\n", " self.theta = cir.parameters()\n",
" \n", " \n",
...@@ -252,19 +252,19 @@ ...@@ -252,19 +252,19 @@
" D_dagger = E\n", " D_dagger = E\n",
"\n", "\n",
" # Encode the quantum state rho_in\n", " # Encode the quantum state rho_in\n",
" rho_BA = to_state(matmul(matmul(E, self.rho_in.data), E_dagger))\n", " rho_BA = E @ self.rho_in @ E_dagger\n",
" \n", " \n",
" # Take partial_trace() to get rho_encode and rho_trash\n", " # Take partial_trace() to get rho_encode and rho_trash\n",
" rho_encode = to_state(partial_trace(rho_BA, 2 ** N_B, 2 ** N_A, 1))\n", " rho_encode = partial_trace(rho_BA, 2 ** N_B, 2 ** N_A, 1)\n",
" rho_trash = to_state(partial_trace(rho_BA, 2 ** N_B, 2 ** N_A, 2))\n", " rho_trash = partial_trace(rho_BA, 2 ** N_B, 2 ** N_A, 2)\n",
"\n", "\n",
" # Decode and get the quantum state rho_out\n", " # Decode and get the quantum state rho_out\n",
" rho_CA = to_state(kron(self.rho_C.data, rho_encode.data))\n", " rho_CA = paddle.kron(self.rho_C, rho_encode)\n",
" rho_out = to_state(matmul(matmul(D, rho_CA.data), D_dagger))\n", " rho_out = D @ rho_CA @ D_dagger\n",
" \n", " \n",
" # Calculate the loss function with rho_trash\n", " # Calculate the loss function with rho_trash\n",
" zero_Hamiltonian = paddle.to_tensor(np.diag([1,0]).astype('complex64'))\n", " zero_Hamiltonian = paddle.to_tensor(np.diag([1, 0]).astype(complex_dtype))\n",
" loss = 1 - real(trace(matmul(zero_Hamiltonian, rho_trash.data)))\n", " loss = 1 - paddle.real(paddle.trace(zero_Hamiltonian @ rho_trash))\n",
"\n", "\n",
" return loss, rho_out\n", " return loss, rho_out\n",
"\n", "\n",
...@@ -320,7 +320,7 @@ ...@@ -320,7 +320,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3 (ipykernel)", "display_name": "Python 3.7.13 ('py3.7_pq2.2.1')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -334,7 +334,7 @@ ...@@ -334,7 +334,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.13" "version": "3.7.13"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
...@@ -348,6 +348,11 @@ ...@@ -348,6 +348,11 @@
"toc_position": {}, "toc_position": {},
"toc_section_display": true, "toc_section_display": true,
"toc_window_display": true "toc_window_display": true
},
"vscode": {
"interpreter": {
"hash": "4e4e2eb86ad73936e915e7c7629a18a8ca06348106cf3e66676b9578cb1a47dd"
}
} }
}, },
"nbformat": 4, "nbformat": 4,
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 变分量子振幅估算"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 概述"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"本教程基于量桨实现单量子比特变分量子振幅估算(Variational Quantum Amplitude Estimation, VQAE)[1] 。在研究变分量子振幅估算前,首先介绍量子振幅估算是什么问题。\n",
"\n",
"假设一个作用在$n+1$个量子比特上的酉算符$A$,$A$对零量子态的作用效果可以表示为:\n",
"\n",
"$$\n",
"A | 0 \\rangle_{n+1}=| \\chi_0 \\rangle_{n+1}\n",
"=\\sqrt{a}| \\psi_{good} \\rangle_{n}| 1 \\rangle\n",
"+\\sqrt{1-a}| \\psi_{bad} \\rangle_{n}| 0 \\rangle\n",
"$$\n",
"\n",
"上式中$a \\in [0,1]$是量子振幅,$| \\psi_{good} \\rangle_{n}$和$| \\psi_{bad} \\rangle_{n}$是一对正交归一的由$n$个量子比特表示的量子态,各自对应一个辅助量子比特。\n",
"\n",
"设想这样一种情况,存在一个可操作的酉算符$A$,但是我们不知道其对应的量子振幅$a$。量子振幅估算问题,顾名思义,就是估算此酉算符$A$所对应的量子振幅$a$。\n",
"\n",
"在含噪中等规模量子 (NISQ) [2] 时代,想要在硬件上更好地实现量子算法,我们需要追求更短的电路深度和更简洁的操作。在此教程中,量子查询复杂度(Quantum query complexity)定义为整个量子电路中应用$A$算符的总次数。通过比较量子查询复杂度(Quantum query complexity)大小,我们可以比较不同估算算法的表现。一般而言,我们希望用更低的量子查询复杂度,实现更高的估算精度。\n",
"\n",
"本教程将介绍三种不同的量子振幅估算算法,分别是经典蒙特卡洛方法、最大似然振幅估算(MLAE)[3] 以及变分量子振幅估算(VQAE)。我们将看到在相同的量子查询复杂度下,MLAE算法相比经典蒙特卡洛算法估算精度有优势。VQAE算法则基于MLAE框架的优化,能显著改善MLAE算法电路过深的问题。"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"# 导入相关包\n",
"import paddle\n",
"from paddle_quantum.gate import Oracle\n",
"from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.state import State, zero_state\n",
"from paddle_quantum.loss import StateFidelity\n",
"import numpy as np\n",
"from numpy import pi, log\n",
"from scipy import optimize\n",
"import warnings\n",
"warnings.filterwarnings('ignore')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"对于$n+1$量子比特的一般情况,$| \\psi_{good} \\rangle_{n}$和$| \\psi_{bad} \\rangle_{n}$为n量子比特展开的$2^n$个基重组构成的一对正交归一量子态。为了更简洁直观,我们考虑单量子比特的情况,将单量子比特展开的2个基$| 0 \\rangle$和$| 1 \\rangle$分别定义为bad state和good state,$A | 0 \\rangle_{1+1}=| \\chi_0 \\rangle_{1+1}=\\sqrt{a}| 1 \\rangle| 1 \\rangle_{anc}+\\sqrt{1-a}| 0 \\rangle| 0 \\rangle_{anc}$。进一步地,在后续用量桨处理时可以略去辅助量子比特,$A | 0 \\rangle_{1}=| \\chi_0 \\rangle_{1}=\\sqrt{a}| 1 \\rangle+\\sqrt{1-a}| 0 \\rangle$。\n",
"\n",
"单量子比特的态可以表示为从布洛赫球的球心到球面上一点的向量。在上述情况中,酉算符$A$的效果可以看作对单量子比特态向量在布洛赫球上的旋转。因此,我们可以选择用一个单量子比特$U3$旋转门来构建酉算符$A$。"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"酉算符A对应的量子振幅为0.25\n"
]
}
],
"source": [
"# 定义酉算符A\n",
"n_qubits = 1 # 考虑单量子比特电路\n",
"amp_operator = Circuit(n_qubits) # 构建单量子比特酉算符\n",
"set_param = pi/3 # 酉算符参数\n",
"amp_param = paddle.to_tensor([set_param, 0, 0], dtype='float32') # 预设酉算符参数\n",
"amp_operator.u3(param=amp_param) # 将参数输入单比特旋转门上构建酉算符\n",
"\n",
"initial_state = zero_state(n_qubits) # 定义初态为零态\n",
"chi_0_state =amp_operator(initial_state) # 酉算符A作用在零态上的效果\n",
"quantum_amp =chi_0_state.measure()['1'] # 预设量子振幅\n",
"print(f\"酉算符A对应的量子振幅为{quantum_amp}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 初代量子振幅估算简介"
]
},
{
"cell_type": "markdown",
"id": "9d6db7db",
"metadata": {},
"source": [
"Brassard等在2002年提出了初代量子振幅估算算法 [4],这个算法($N_{q-AE}\\sim O(1/\\epsilon)$)理论上相比经典蒙卡采样($N_{q-MC}\\sim O(1/\\epsilon^2)$)实现了平方量子加速。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"量子振幅增大(Quantum amplitude amplification)是Grover搜索算法的拓展,也是解决振幅估算(Amplitude Estimation)问题的基础。\n",
"\n",
"考虑一般情况,对于一个作用在$n+1$个量子比特(“+1”为辅助比特)上的酉算符$A$,引入增大算符$Q$:\n",
"\n",
"$$\n",
"Q=-R_{\\chi}R_{good} \\tag{1}\n",
"$$\n",
"\n",
"其中:\n",
"\n",
"$$\n",
"R_{\\chi}=I-2| \\chi_0 \\rangle_{n+1} \\langle\\chi_0|_{n+1}=I-2A| 0 \\rangle_{n+1}\\langle 0|_{n+1}A^\\dagger \\tag{2}\n",
"$$\n",
"\n",
"$$\n",
"R_{good}=I-2| \\psi_{good} \\rangle_{n} \\langle \\psi_{good}|_n \\otimes | 1 \\rangle\\langle 1 | \\tag{3}\n",
"$$\n",
"\n",
"在以 $| \\psi_{good} \\rangle_{n}| 1 \\rangle$和$| \\psi_{bad} \\rangle_{n}| 0 \\rangle$为正交基所展开的二维子空间$H_\\chi$中,$R_{\\chi}$和$R_{good}$ 均为反射算符。\n",
"\n",
"我们可以将$| \\chi_0 \\rangle_{n+1}$看作一个在二维子空间$H_\\chi$中的矢量。\n",
"\n",
"$$\n",
"| \\chi_0 \\rangle_{n+1}=\\cos(\\theta)\n",
"| \\psi_{bad} \\rangle_{n}| 0 \\rangle+\\sin(\\theta)| \\psi_{good} \\rangle_{n}| 1 \\rangle \\tag{4}\n",
"$$\n",
"\n",
"其中$\\theta$可以认为是矢量$| \\chi_0 \\rangle$和$| \\psi_{bad} \\rangle| 0 \\rangle$所代表的轴之间的夹角。其中我们定义量子振幅为:$a=\\sin^2(\\theta)$。\n",
"\n",
"当我们把增大算符$Q$作用在$| \\chi_0 \\rangle_{n+1}$上m次时,得到:\n",
"\n",
"$$\n",
"| \\chi_m \\rangle_{n+1}=Q^m| \\chi_0 \\rangle_{n+1}=\\cos[(2m+1)\\theta]\n",
"| \\psi_{bad} \\rangle_{n}| 0 \\rangle+\\sin[(2m+1)\\theta]| \\psi_{good} \\rangle_{n}| 1 \\rangle \\tag{5}\n",
"$$\n",
"\n",
"\n",
"\n",
"因此我们可以直观地理解,增大算符$Q$对$| \\chi_0 \\rangle_{n+1}$的作用为在空间$H_\\chi$中向$| \\psi_{good} \\rangle_{n}| 1 \\rangle$旋转$2\\theta$角。\n",
"\n",
"当选择合适的增大次数$m=\\lfloor \\frac{1}{2}(\\frac{\\pi}{2\\theta}-1)\\rfloor$时,量子振幅$a_m=\\sin[(2m+1)\\theta]\\approx1$。\n",
"\n",
"接下来我们基于量桨构建增大算符$Q$。"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"# 构造反射算子R_chi\n",
"identity = paddle.to_tensor([[1, 0],[0, 1]], dtype=\"complex64\") # 定义单位矩阵\n",
"density_matrix_0 = chi_0_state.ket @ chi_0_state.bra # amp_0_state的密度算符形式\n",
"r_chi = identity - paddle.to_tensor([2], dtype=\"complex64\") * density_matrix_0 #构建相对chi态的反射算符"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"# 构造反射算子R_good\n",
"flip = Circuit(n_qubits)\n",
"flip.x() \n",
"one_state = flip(initial_state) # 构建“1”量子态,在我们讨论中“1”态定义为good state\n",
"density_matrix_good = one_state.ket @ one_state.bra # Good state的密度算符\n",
"r_good = identity - paddle.to_tensor([2], dtype=\"complex64\") * density_matrix_good #构建相对good state的反射算符"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"# 构造增大算符Q\n",
"num_amplification = 1 # 增大算符应用次数\n",
"Q_operator = Oracle(paddle.to_tensor([-1], dtype=\"complex64\") * r_chi @ r_good, qubits_idx=0, num_qubits=1, depth=num_amplification) #利用Oracle构建增大算符,其中深度为应用算符的次数"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"根据定义$a = \\sin^2(\\theta)$,所以估算量子振幅$a$可以被转化为估算$\\theta$。我们注意到增大算符$Q=-R_{\\chi}R_{good}=e^{-i2\\theta Y}$,$| \\chi_0 \\rangle_{n+1}$为$Q$的特征向量且特征值为$e^{\\pm2i\\theta}$,所以可以对$Q$进行量子相位估算(quantum phase estimation)来推测$\\theta$的取值,具体量子电路如下图所示。"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "76f5e510",
"metadata": {},
"source": [
"![QPE](./figures/VQAE-fig-qpe.png \"图 1: 量子振幅估计电路 [4]。\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"然而如上图所示,初代量子振幅估算算法中使用到的量子相位估计电路包括量子傅里叶变换和多个控制门,这意味着在含噪声的中型量子设备上物理实现初代量子振幅估算是很困难的。所以人们一直在尝试提出更优化的量子振幅估算算法以降低其对量子硬件的要求,便于物理上实现此算法以展示量子优越性。\n",
"\n",
"本教程的主题变分量子振幅估算(VQAE)就是最近被提出的一种优化算法。变分量子振幅估算是在最大似然振幅估算(MLAE)的算法框架下,增加了变分近似过程,以控制了MLAE算法所需的量子电路深度,因为更浅的量子电路在物理上更易于实现。VQAE和MLAE相比经典蒙特卡洛方法,都能以更低的量子查询复杂度实现相同的估算精度。\n",
"\n",
"所以接下来我们将介绍并用量桨实现经典蒙特卡洛方法、最大似然振幅估算算法和变分量子振幅估算。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 经典蒙特卡洛方法"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"经典蒙特卡洛方法是指通过重复多次制备态$A| 0 \\rangle_{n+1}=\\sqrt{a}| \\psi_{good} \\rangle_{n}| 1 \\rangle+\\sqrt{1-a}| \\psi_{bad} \\rangle_{n}| 0 \\rangle$并测量辅助量子比特。通过计算测量结果中辅助量子比特为$| 1 \\rangle$态的概率,来估算量子振幅$a$。下面我们用量桨来尝试实现,考虑单量子比特的情况,我们可以选择不引入辅助量子比特。"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "d8c88075",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"经典蒙特卡洛方法对量子振幅的估算结果是a = 0.24505, 误差绝对值为 0.00495000000000001,相对误差为1.980000000000004%\n",
"经典蒙特卡洛方法查询复杂度为20000\n"
]
}
],
"source": [
"# 经典蒙特卡洛方法\n",
"N_sample_mc = 20000 # 采样总量\n",
"N_good = 0 # 初始化测得good state的次数\n",
"for i in range(N_sample_mc):\n",
" result = chi_0_state.measure(shots=1) # 单次测量(chi态的构建需要应用一次酉算符A)\n",
" N_good += result['1'] # 如果测量结果为1,对应计数加一\n",
"amp_mc = N_good/N_sample_mc # 通过测量结果为1的概率估算量子振幅a\n",
"error_mc = abs(amp_mc - quantum_amp) # 估算值与预设值的误差绝对值\n",
"rate_mc = error_mc/quantum_amp # 估算值的相对误差\n",
"print(f\"经典蒙特卡洛方法对量子振幅的估算结果是a = {amp_mc}, 误差绝对值为 {error_mc},相对误差为{100*rate_mc}%\")\n",
"print(f\"经典蒙特卡洛方法查询复杂度为{N_sample_mc}\")"
]
},
{
"cell_type": "markdown",
"id": "4177f22f",
"metadata": {},
"source": [
"经典蒙特卡洛算法作为一种最显而易见的解决方案,可以实现一定精度的振幅估算。估算精度由进行采样的次数决定,更多的采样次数会得到更高精度的振幅估算结果。但我们也注意到,在此方法中每进行一次测量都需要使用一次酉算符$A$来构造$| \\chi_0 \\rangle$态。因此,经典蒙特卡洛算法的查询复杂度等于采样总量。然而我们相信量子振幅估计算法能够提供更优的结果,即使用更低的量子查询复杂度来实现相同的估算精度,这也被称为量子加速(Quantum speedup)。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 最大似然振幅估算(MLAE)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"相比初代量子振幅估算,最大似然振幅估算算法(MLAE)规避了量子相位估算,通过将多条量子线路合并分析,来进行振幅估算。算法具体步骤如下:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**第一步**:选取一个集合$\\{m_k\\}$,此集合中的每一项代表了应用增大算符$Q$的次数,比如$m_k$对应$Q^{m_k}| \\chi_0 \\rangle_{n+1}$。\n",
"\n",
"测量$Q^{m_k}| \\chi_0 \\rangle_{n+1}$的辅助量子比特,进而确定量子态属于good state还是bad state。测量得到good state的概率为$\\sin^2((2m_k+1)\\theta_a)$\n",
"\n",
"对于态$Q^{m_k}| \\chi_0 \\rangle_{n+1}$我们用$N_k$来表示测量的总次数,$h_k$来表示测得good state的次数。\n",
"\n",
"进而我们可以写出对于集合第k项的似然函数:\n",
"\n",
"$$\n",
"L_k(h_k;\\theta_a)=[\\sin^2((2m_k+1)\\theta_a)]^{h_k}\n",
"[\\cos^2((2m_k+1)\\theta_a)]^{N_k-h_k} \\tag{6}\n",
"$$\n",
"\n",
"**第二步**:将集合$\\{m_0,......,m_M\\}$中每一项对应的似然函数$L_k(h_k;\\theta_a)$合并成一个总似然函数\n",
"\n",
"$$\n",
"L(\\mathbf{h};\\theta_a)= \\prod^{M}_{k=0}L_k(h_k;\\theta_a) \\tag{7}\n",
"$$\n",
"\n",
"其中$\\mathbf{h}=(h_0,h_1,...,h_M)$\n",
"\n",
"**第三步**:最大似然振幅估算定义是找到使$L(\\mathbf{h};\\theta_a)$取最大值的$\\theta_a$。故$\\theta_a$的表达式可以写为:\n",
"\n",
"$$\n",
"\\hat{\\theta_a}=\\arg{\\max_{\\theta_a}{L(\\mathbf{h};\\theta_a)}} \\tag{8}\n",
"=\\arg{\\max_{\\theta_a}{\\text{ln}[L(\\mathbf{h};\\theta_a)]}}\n",
"$$\n",
"\n",
"最终MLAE的结果为:\n",
"\n",
"$$\n",
"\\hat{a}=\\sin^2\\hat{\\theta_a} \\tag{9}\n",
"$$\n",
"\n",
"MLAE的完整过程如下图所示:"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "6dcf46cc",
"metadata": {},
"source": [
"![MLAE](./Figures/VQAE-fig-mlae.png \"MLAE 原理示意图 [3]。\")"
]
},
{
"cell_type": "markdown",
"id": "b34937f2",
"metadata": {},
"source": [
"选择合适的$\\{m_k, N_k\\}$,可以使得估算结果与经典算法对比体现较明显的优势。一般有两种选择方式:\n",
"\n",
"1. 线性增加(Linearly incremental sequence, LIS): $N_k = N_{shot}, \\forall k$,and $m_k = k$. i.e.$m_0=0, m_1=1, ...,m_M=M$\n",
" \n",
" 当$M\\gg1$时,预测误差$\\hat{\\epsilon}\\sim N_q^{-3/4}$,虽然没达到上述提及的最佳情况,但与经典蒙特卡洛方法相比,仍然能体现量子优势。\n",
" \n",
"2. 指数增加(Expoentially incremental sequence, ELS):$N_k = N_{shot}, \\forall k$,and $m_k = 2^{k-1}$。i.e. $m_0=0, m_1=2^0, ..., m_M=2^{M-1}$\n",
" \n",
" 预测误差$\\hat{\\epsilon}\\sim N_q^{-1}$\n",
" \n",
"\n",
"在变分量子振幅估算(VQAE)中,用到的是第一种线性增加序列。"
]
},
{
"cell_type": "markdown",
"id": "7673d10a",
"metadata": {},
"source": [
"### MLAE量桨实现 "
]
},
{
"cell_type": "markdown",
"id": "25488db9",
"metadata": {},
"source": [
"下面我们用量桨尝试实现最大似然量子振幅估算(MLAE)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# 初始化MLAE相关参数(对应于上述教程中的MLAE第一步)\n",
"M = 25 # 使用增大算符Q的最大次数\n",
"m = np.arange(0, M, 1) # 线性增加序列 LIS\n",
"N_k = 25 # 采样总次数\n",
"h_k = np.zeros(M) # 初始化数据空间以用于存储测得Good state次数\n",
"\n",
"# 采样过程\n",
"for k in range(M):\n",
" Q_operator_MLAE = Oracle(paddle.to_tensor([-1], dtype=\"complex64\") * r_chi @ r_good, qubits_idx=0, num_qubits=1, depth=k)\n",
" for i in range(N_k):\n",
" rotated_state = Q_operator_MLAE(chi_0_state) # 将增大算符作用在initial state上\n",
" result = rotated_state.measure(shots = 1) # 测量并统计good state(测得“1”)的次数\n",
" h_k[k] += result['1'] # 对于不同的k,记录测得good state的次数"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"# 定义总似然函数(对数形式)(对应MLAE第二步)\n",
"params = ()\n",
"def likelihood(z, *params):\n",
" theta = z\n",
" f = 0\n",
" for i in range(M):\n",
" f = f + log(np.sin((2 * m[i] + 1) * theta) ** (2 * h_k[i]) * np.cos((2 * m[i] + 1) * theta) ** (2 * (N_k - h_k[i]))) #相乘转化为ln函数相加\n",
" return (-f) # 加负号使得后续算法求得的最小值对应于f的最大值"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"总似然函数的极大值为 -241.93038251847616 在theta = 0.5232857156858379 rad时取得\n",
"MLAE量子振幅估算结果为0.2497289311838693, 估算绝对误差为0.000271068816130704, 相对误差为0.1084275264522816%\n"
]
}
],
"source": [
"# 使用Brute-force搜索算法来求总似然函数的极值(对应MLAE第三步)\n",
"rranges = (0, pi/2, pi/200) # Brute-force算法网格点的范围\n",
"resbrute = optimize.brute(likelihood, (rranges,), args=params, full_output=True, finish=optimize.fmin) #调用Brute-force算法\n",
"theta_a = resbrute[0][0] # 最小值对应的theta值\n",
"amp_MLAE = np.sin(theta_a) ** 2 # MLAE量子振幅估算值\n",
"error_MLAE = abs(amp_MLAE - quantum_amp) # 计算相对预设值的绝对误差\n",
"rate_MLAE = error_MLAE/quantum_amp # 相对误差\n",
"print(f\"总似然函数的极大值为 {-resbrute[1]} 在theta = {resbrute[0][0]} rad时取得\")\n",
"print(f\"MLAE量子振幅估算结果为{amp_MLAE}, 估算绝对误差为{error_MLAE}, 相对误差为{100 * rate_MLAE}%\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"根据上述算法过程,最大似然量子振幅估算的量子查询复杂度$N_{q-MLAE}$为:\n",
"\n",
"$$\n",
"N_{q-MLAE}=\\sum^{M}_{k=0}N_k(2m_k+1) \\tag{10}\n",
"$$\n",
"\n",
"其中$2m_k$代表着应用$m_k$次增大算符$Q$且每一次应用都需调用一次$A$和一次$A^\\dagger$($Q = -(I-2A| 0 \\rangle_{n+1} \\langle 0 |_{n+1}A^\\dagger) (I-2| \\psi_{good} \\rangle_{n}\\langle \\psi_{good}|_{n} \\otimes | 1 \\rangle\\langle 1 |) $);“+1”来自于制备初态 $| \\chi_0 \\rangle_{n+1}$时使用了一次算符$A$ ($| \\chi_0 \\rangle_{n+1}\n",
"=A| 0 \\rangle_{n+1})$;$N_k$代表对第$k$项的采样次数。"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"MLAE的量子查询复杂度为15625\n"
]
}
],
"source": [
"# 计算MLAE量子查询复杂度\n",
"N_q_MLAE = 0\n",
"for i in range(M):\n",
" N_q_MLAE += N_k * (2 * i + 1)\n",
"print(f\"MLAE的量子查询复杂度为{N_q_MLAE}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 最大似然振幅估算与经典蒙特卡洛对比"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"对同一个酉算符$A$的振幅估算问题,分别使用最大似然振幅估算和经典蒙特卡洛算法。比较两个算法估算误差$\\epsilon$与查询复杂度$N_q$之间的关系。例如,设定酉算符的输入参数为$\\pi/8$,保持两算法量子查询复杂度相同,比较估算值误差大小。数值实验结果如下图所示:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![comparison](./Figures/VQAE-fig-comparison.png \"图 3: MLAE 和 CMC 算法表现对比。\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"上图体现了MLAE算法在相同的量子查询复杂度下,估算误差大约比经典蒙特卡洛算法小一个数量级。这个结果体现了MLAE相比经典蒙特卡洛算法的优越性。当估算量子振幅越小时,MLAE相较经典蒙特卡洛的优势会越明显。读者可根据上文已提供的算法代码自行尝试验证。"
]
},
{
"cell_type": "markdown",
"id": "4b86a83e",
"metadata": {},
"source": [
"## 变分量子振幅估算(VQAE) "
]
},
{
"cell_type": "markdown",
"id": "7bab7472",
"metadata": {},
"source": [
"基于使用线性增大序列(LIS)$\\{m=1,2,3,...,M\\}$的MLAE算法的量子电路深度以$2m+1$的趋势增长。量子电路深度过大会增大其物理实现难度,所以提出了VQAE算法 [1] 来防止线路深度随$m$无限地增长。\n",
"\n",
"VQAE基于MLAE的算法框架增加了一个变分过程(variational step)。态$| \\chi_m \\rangle_{n+1}$会周期性地近似为一个深度为1的变分量子态。对于MLAE算法中的线性增加序列,我们选择一个正整数$k$作为变分周期,每到达$Q^k\\ (0<k<M)$就进行一次变分近似过程。"
]
},
{
"cell_type": "markdown",
"id": "6dadbc7e",
"metadata": {},
"source": [
"### VQAE的算法结构(伪代码)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![pesudo](./Figures/VQAE-fig-pesudo.png \"图 4: VQAE 伪代码。\")"
]
},
{
"cell_type": "markdown",
"id": "4f167e02",
"metadata": {},
"source": [
"使用得到的$\\{h_m\\}$,计算每一项的似然函数$L_m(h_m;\\theta_a)$,继续进行最大似然近似。\n",
"\n",
"由与MLAE相同的步骤,我们可以得到振幅估算为:\n",
"\n",
"$$\n",
"\\hat{\\theta_a}\n",
"=\\arg{\\max_{\\theta_a}{\\text{ln}[L(\\{h_m\\};\\theta_a)]}} \\tag{11}\n",
"$$"
]
},
{
"cell_type": "markdown",
"id": "2832b3c5",
"metadata": {},
"source": [
"### 变分近似过程"
]
},
{
"cell_type": "markdown",
"id": "8642bfcb",
"metadata": {},
"source": [
"变分近似是VQAE算法中的核心步骤。在这个过程中,设变分周期为$k$,我们将一个线路深度为$2k+1$的态$Q^k | \\phi_i \\rangle_{n+1}$(目标态)用线路深度为1的变分量子态 $| \\phi_{var}(\\mathbf{\\lambda}) \\rangle_{n+1}$近似替换,其中$\\mathbf{\\lambda}$代表若干变分参数。\n",
"\n",
"我们希望目标态$Q^k | \\phi_i \\rangle_{n+1}$和变分量子态$| \\phi_{var}(\\mathbf{\\lambda}) \\rangle_{n+1}$尽可能接近,我们用目标态和变分量子态之间的保真度来衡量这两个态的相似程度,保真度$F(\\lambda)$定义为:\n",
"\n",
"$$\n",
"F(\\lambda)=Re[\\langle \\phi_{var}(\\mathbf{\\lambda})|_{n+1}Q^k | \\phi_i \\rangle_{n+1}] \\tag{12}\n",
"$$\n",
"\n",
"\n",
"\n",
"\n",
"变分近似的最优解可以写为:\n",
"\n",
"$$\n",
"| \\phi_{i+1} \\rangle_{n+1}=| \\phi_{var}(\\tilde{\\mathbf{\\lambda}}) \\rangle_{n+1} \n",
"\\quad\n",
"\\tilde{\\mathbf{\\lambda}}=\\arg{\\max_{\\mathbf{\\lambda}}{F(\\mathbf{\\lambda})}} \\tag{13}\n",
"$$\n",
"\n",
"在代码实践中,我们可以将非保真度 $\\text{Infidelity}(\\lambda) = 1 - F(\\lambda)$ 定义为损失函数,并用Adam优化器寻找使损失函数最小的参数值。损失函数取最小值时保真度取最大值,对应变分量子态和目标态最接近的情况。注意到这里计算损失函数的量子线路深度为$2k+2$,为整个VQAE算法中线路深度最大的部分。\n",
"\n",
"变分量子态$| \\phi_{var}(\\mathbf{\\lambda}) \\rangle_{n+1}$为一个参数化量子线路(PQC),可以表示为:\n",
"\n",
"$$\n",
"\\left|\\phi_{\\text {var }}(\\boldsymbol{\\lambda})\\right\\rangle_{n+1}=\\mathcal{U}_{\\text {var }}(\\boldsymbol{\\lambda})\\left|\\phi_{\\text {init }}\\right\\rangle_{n+1}\n",
"=\\prod_{j} e^{-\\mathrm{i} \\lambda_{j} G_{j}}\\left|\\phi_{\\text {init }}\\right\\rangle_{n+1} \\tag{14}\n",
"$$\n",
"\n",
"后续代码实践中,变分量子态为单量子比特参数化量子电路,每一层由一个$R_y(\\theta)$单比特旋转门和$R_z(\\theta)$单比特旋转门组成。变分量子态的初态为$| \\phi_{\\text{init}} \\rangle_{n+1}=| \\chi_0 \\rangle_{n+1}$,变分周期为5。"
]
},
{
"cell_type": "markdown",
"id": "6cfdae51",
"metadata": {},
"source": [
"### VQAE量桨实现"
]
},
{
"cell_type": "markdown",
"id": "b3997e65",
"metadata": {},
"source": [
"接下来我们尝试用量桨实现变分量子振幅估算(VQAE)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"# 定义VQAE算法中用到的函数\n",
"\n",
"def construct_Q(input_param: float, n_qubits: int, num_amplification):\n",
" r\"\"\" 构建增大算符Q\n",
" \n",
" Args:\n",
" input_param: 输入参数, 用于U3旋转门\n",
" n_qubits: 线路量子比特数\n",
" num_amplification: 使用增大算符的次数\n",
" \n",
" Returns:\n",
" Oracle: 对应参数和深度的增大算符\n",
" \n",
" \"\"\"\n",
" # 定义酉算符A\n",
" amp_operator = Circuit(n_qubits) \n",
" amp_param = paddle.to_tensor([input_param, 0, 0], dtype='float32') #预设酉算符参数\n",
" amp_operator.u3(param=amp_param)\n",
"\n",
" initial_state = zero_state(n_qubits) # 定义初态为零态\n",
" chi_0_state =amp_operator(initial_state) # 酉算符A作用在零态上的效果\n",
"\n",
" # 构造反射算子R_chi\n",
" identity = paddle.to_tensor([[1, 0],[0, 1]], dtype=\"complex64\") # 定义单位矩阵\n",
" density_matrix_0 = chi_0_state.ket @ chi_0_state.bra # amp_0_state的密度算符形式\n",
" r_chi = identity - paddle.to_tensor([2], dtype=\"complex64\") * density_matrix_0\n",
"\n",
" # 构造反射算子R_good\n",
" flip = Circuit(n_qubits)\n",
" flip.x()\n",
" one_state = flip(initial_state) #构建“1”量子态,在我们讨论中“1”态定义为good state\n",
" density_matrix_good = one_state.ket @ one_state.bra #Good state的密度算符\n",
" r_good = identity - paddle.to_tensor([2], dtype=\"complex64\") * density_matrix_good\n",
" \n",
" # 返回增大算符Q\n",
" return Oracle(paddle.to_tensor(paddle.to_tensor([-1], dtype=\"complex64\") * r_chi @ r_good), qubits_idx=0, num_qubits=n_qubits, depth=num_amplification)\n",
"\n",
"\n",
"def loss_fcn(input_state: State, period: int, start_state: State, target_param: float) -> paddle.Tensor:\n",
" r\"\"\" 计算神经网络的损失函数\n",
" \n",
" Args:\n",
" input_state: 输入态\n",
" period: 变分周期\n",
" start_state: 当前变分态,用于构建目标态\n",
" target_param: 目标态参数\n",
" \n",
" Returns:\n",
" loss: 损失函数值, 损失函数定义为输入态和目标态之间的非保真度(Infidelity)\n",
" \n",
" \"\"\"\n",
"\n",
" Q_var = construct_Q(target_param, int(1), period)\n",
" Target_state = Q_var(start_state) \n",
" loss = 1 - StateFidelity(Target_state)(input_state)\n",
" return loss\n",
"\n",
"\n",
"def cir_constructor(depth: int) -> Circuit:\n",
" r\"\"\" 构建变分量子电路\n",
" \n",
" Args:\n",
" depth: 电路深度\n",
" \n",
" Returns:\n",
" 变分量子电路\n",
" \n",
" Note:\n",
" 单量子比特, 一层定义为Ry和Rz任意角度旋转门, 旋转角度为电路参数, 变分量子电路深度为3\n",
" \n",
" \"\"\"\n",
" cir = Circuit(1)\n",
" \n",
" for _ in range(depth):\n",
" cir.ry()\n",
" cir.rz()\n",
" return cir"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"# 初始化相关参数(注:VQAE仍使用MLAE框架)\n",
"M = 25 # 使用增大算符Q的最大次数\n",
"m = np.arange(0, M, 1) # 线性增加序列 LIS\n",
"N_k_vqae = 50 # 采样总次数"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<paddle.fluid.core_noavx.Generator at 0x7fd89399e930>"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# 变分近似定义\n",
"# 超参数设置\n",
"theta_size = 6 # 线路深度决定的参数数量\n",
"ITR = 500 # 设置迭代次数\n",
"LR = 0.001 # 设置学习速率\n",
"SEED = 1 # 固定随机数种子\n",
"paddle.seed(SEED)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=================================\n",
"第 1 次变分近似\n",
"损失函数的最小值是: 0.011207818984985352\n",
"变分参数值为 [-0.3985748 4.4845576 3.8463612 0.5077383 2.1137552 5.696977 ]\n",
"变分态相比目标态的保真度为0.9888777136802673\n",
"=================================\n",
"第 2 次变分近似\n",
"损失函数的最小值是: 0.022154152393341064\n",
"变分参数值为 [3.5385787 3.686828 3.778493 0.8545992 3.3457727 5.151529 ]\n",
"变分态相比目标态的保真度为0.9780024886131287\n",
"=================================\n",
"第 3 次变分近似\n",
"损失函数的最小值是: 0.12489771842956543\n",
"变分参数值为 [2.9717565 4.5942483 3.8417864 1.0915956 2.9404602 4.45632 ]\n",
"变分态相比目标态的保真度为0.8756691813468933\n",
"=================================\n",
"第 4 次变分近似\n",
"损失函数的最小值是: 0.07246685028076172\n",
"变分参数值为 [5.293609 5.0283127 1.7944657 3.575252 2.10594 4.7431984]\n",
"变分态相比目标态的保真度为0.9278923273086548\n",
"=================================\n",
"第 5 次变分近似\n",
"损失函数的最小值是: 0.007037162780761719\n",
"变分参数值为 [ 2.9615374 6.2987323 2.9560285 -0.05320463 1.5106332 3.6751766 ]\n",
"变分态相比目标态的保真度为0.993030846118927\n"
]
}
],
"source": [
"# 采样和变分近似过程\n",
"start_state = chi_0_state # start_state是变分量子态,算法开始时为chi_0_state\n",
"cycle = 5 # 触发变分近似的周期\n",
"h_k_vqae = np.zeros(M) # 初始化数据空间以用于存储测得Good state次数\n",
"for k in range(M):\n",
" i = np.floor(k / cycle)\n",
" j = k % cycle\n",
" Q_operator_VQAE = construct_Q(set_param, int(1), j)\n",
" for sample in range(N_k_vqae):\n",
" rotated_state = Q_operator_VQAE(start_state) # 将增大算符作用在变分态上\n",
" result = rotated_state.measure(shots = 1) # 测量并统计good state(测得“1”)的\n",
" h_k_vqae[k] += result['1'] # 对于不同的k,测得good state的次数\n",
"\n",
" if j == cycle - 1: # 触发变分近似过程的条件\n",
" # 记录中间优化结果\n",
" loss_list = []\n",
"\n",
" # 定义变分电路\n",
" cir = cir_constructor(3)\n",
"\n",
" # 利用Adam优化器.\n",
" opt = paddle.optimizer.Adam(learning_rate=LR, parameters=cir.parameters())\n",
" \n",
" # 优化循环\n",
" for itr in range(ITR):\n",
" # 向前传播计算损失函数\n",
" loss = loss_fcn(cir(chi_0_state), cycle, start_state, set_param)\n",
"\n",
" # 反向传播优化损失函数\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
"\n",
" # 记录学习曲线\n",
" loss_list.append(loss.item())\n",
" \n",
" print(\"=================================\")\n",
" print(f'第 {int(i + 1)} 次变分近似')\n",
" print(f'损失函数的最小值是: {loss_list[-1]}')\n",
" print(f\"\\r变分参数值为 {cir.param.numpy()}\")\n",
" \n",
" Q_test = construct_Q(set_param, int(1), cycle)\n",
" test_state = Q_test(start_state) # 更新用于计算保真度的测试态\n",
" \n",
" start_state = cir(chi_0_state) # 更新变分量子态\n",
" start_state.data.stop_gradient = True \n",
" print(f\"变分态相比目标态的保真度为{StateFidelity(test_state)(start_state).numpy()[0]}\")"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"# 定义总似然函数(对数形式)(对应MLAE第二步)\n",
"params = ()\n",
"def likelihood_vqae(z, *params):\n",
" theta = z\n",
" f = 0\n",
" for i in range(M):\n",
" f = f + log(np.sin((2 * m[i] + 1) * theta) ** (2 * h_k_vqae[i]) * np.cos((2 * m[i] + 1) * theta) ** (2 * (N_k_vqae - h_k_vqae[i]))) #相乘转化为ln函数相加\n",
" return (-f) # 加负号使得后续算法求得的最小值对应于f的最大值"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=================================\n",
"总似然函数的极大值为 -691.84028156814 在theta = 0.532245313972681 rad时取得\n",
"VQAE量子振幅估算结果为0.2575251290528533, 估算绝对误差为0.007525129052853297, 相对误差为3.0100516211413186%\n"
]
}
],
"source": [
"# 使用Brute-force搜索算法来求总似然函数的极值(对应MLAE第三步)\n",
"rranges = (0, pi/2, pi/200) # Brute-force算法网格点的范围\n",
"resbrute = optimize.brute(likelihood_vqae, (rranges,), args=params, full_output=True, finish=optimize.fmin) #调用Brute-force算法\n",
"theta_a = resbrute[0][0] # 最小值对应的theta值\n",
"amp_VQAE = np.sin(theta_a) ** 2 # MLAE量子振幅估算值\n",
"error_VQAE = abs(amp_VQAE - quantum_amp) # 计算相对预设值的绝对误差\n",
"rate_VQAE = error_VQAE/quantum_amp # 相对误差\n",
"print(\"=================================\")\n",
"print(f\"总似然函数的极大值为 {-resbrute[1]} 在theta = {resbrute[0][0]} rad时取得\")\n",
"print(f\"VQAE量子振幅估算结果为{amp_VQAE}, 估算绝对误差为{error_VQAE}, 相对误差为{100 * rate_VQAE}%\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"由此可得,VQAE算法也实现了精度较高的振幅估算,与原MLAE算法相比最大量子电路深度显著减小。例如,考虑M=50且变分周期为5的情况,MLAE算法最多需要对$| \\chi_0 \\rangle$连续应用50次增大算符$Q$,而VQAE算法的量子电路最多连续应用5次,为MLAE的十分之一。这说明使用VQAE算法可以有效地减少量子电路深度,有助于量子振幅估算在NISQ设备上实现。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"VQAE算法的量子查询复杂度由变分复杂度$N_{var}$和采样复杂度$N_{samp}$两部分组成。\n",
"$$\n",
"N = N_{var} + N_{samp} \\tag{15}\n",
"$$\n",
"$$\n",
"N_{var} = N_{var/1}(2k+2)\\lfloor M/k \\rfloor \\sim O(k\\lfloor M/k \\rfloor) \\tag{16}\n",
"$$\n",
"$$\n",
"N_{samp} = hk(k+2)\\lfloor M/k \\rfloor + h(M\\%k)(M\\%k+2) \\tag{17}\n",
"$$\n",
"其中$M$为MLAE线性增大序列中的最大值,$k$为VQAE中的变分周期,$h$为采样过程中对每个态重复采样的次数。$N_{var/1}=2n_fn_sn_p$, 一次变分过程中需要运行的量子电路总量,其中$n_p$为参数化量子电路的参数数量,$n_s$是参数化量子电路的参数扫描次数,$n_f$是每次计算目标函数时伯努利试验的次数(在硬件实现中可以认为约等于$n_s$)。具体分析可查看参考文献[1]。"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"采样复杂度为 8750\n",
"变分复杂度为 180000000\n",
"VQAE算法的量子查询复杂度为 180008750\n"
]
}
],
"source": [
"# 计算VQAE量子查询复杂度\n",
"from math import floor\n",
"M_vqae = M\n",
"k_cycle = cycle\n",
"n_p = theta_size\n",
"n_s = ITR\n",
"n_f = n_s\n",
"N_var1 = 2 * n_p * n_s * n_f\n",
"N_var = N_var1 * (2 * k_cycle + 2) * floor(M_vqae/k_cycle)\n",
"N_samp = N_k_vqae * k_cycle * (k_cycle + 2) * floor(M_vqae/k_cycle) + N_k_vqae * (M_vqae % k_cycle) * (M_vqae % k_cycle + 2)\n",
"print(f\"采样复杂度为 {N_samp}\")\n",
"print(f\"变分复杂度为 {N_var}\")\n",
"print(f\"VQAE算法的量子查询复杂度为 {N_var + N_samp}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们注意到VQAE算法的量子查询复杂度很高,其主要来自于变分近似过程的复杂度。这是减少量子电路深度所引入的代价。引入的变分过程并非越多越好,量子电路的深度和变分过程的复杂度之间存在一个权衡关系。但是可以通过一些优化设计,降低变分近似过程中的量子查询复杂度。比如文献[1]中提到的Adaptive VQAE,感兴趣的读者可自行参看。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 总结"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"本教程主要内容为用量桨实现变分量子振幅估算(VQAE)。教程首先介绍了量子振幅估算问题和初代算法物理实现的难度。进而介绍了最大似然振幅估算(MLAE),MLAE规避了初代算法主要的物理实现难点——量子相位估算,但是存在量子电路深度过大的问题,也不利于物理实现。为了降低MLAE算法的量子电路深度,我们引入了一个变分近似过程得到了变分量子振幅估算,VQAE相比MLAE可以显著降低量子电路的深度。VQAE算法的提出进一步提高了量子振幅估算物理实现的可能性。但同时需注意,引入变分过程时会带来大量变分复杂度,变分过程和量子电路深度之间存在权衡关系。\n",
"\n",
"教程基于量桨实现了单量子比特的MLAE和VQAE,展示了对预设量子振幅的低误差预测。并且比较了上述算法与经典蒙特卡洛算法的预测精度和量子查询复杂度,通过数值实验展示了量子算法相比经典算法的加速效应。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"_______\n",
"## 参考文献"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[1] Plekhanov, Kirill, et al. \"Variational quantum amplitude estimation.\" Quantum 6 (2022): 670. https://quantum-journal.org/papers/q-2022-03-17-670/\n",
"\n",
"[2] Preskill, John. \"Quantum computing in the NISQ era and beyond.\" Quantum 2 (2018): 79. https://quantum-journal.org/papers/q-2018-08-06-79/\n",
"\n",
"[3] Suzuki, Yohichi, et al. \"Amplitude estimation without phase estimation.\" Quantum Information Processing 19.2 (2020): 1-17. https://link.springer.com/article/10.1007/s11128-019-2565-2\n",
"\n",
"[4] Brassard, Gilles, et al. \"Quantum amplitude amplification and estimation.\" Contemporary Mathematics 305 (2002): 53-74. https://arxiv.org/pdf/quant-ph/0005055.pdf"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.7.13 ('py3.7_pq2.2.1')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.13"
},
"vscode": {
"interpreter": {
"hash": "4e4e2eb86ad73936e915e7c7629a18a8ca06348106cf3e66676b9578cb1a47dd"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Variational quantum amplitude estimation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Backgrounds"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This tutorial implements single-qubit variational quantum amplitude estimation (VQAE) [1] based on Paddle Quantum. Before getting into VQAE, let's first review quantum amplitude estimation. \n",
"\n",
"In general, assume there is an operation for $n+1$ qubits denoted as unitary operator $A$, and the effect of applying it on a zero state can be written as:\n",
"\n",
"$$\n",
"A | 0 \\rangle_{n+1}=| \\chi_0 \\rangle_{n+1}\n",
"=\\sqrt{a}| \\psi_{good}\\rangle_{n}| 1 \\rangle\n",
"+\\sqrt{1-a}| \\psi_{bad}\\rangle_{n}| 0 \\rangle,\n",
"$$\n",
"\n",
"where good state $| \\psi_{good }\\rangle_n$ and bad state $| \\psi_{bad }\\rangle_n$ are orthonormal quantum states of $n$ qubits, each of them corresponds to an ancillary qubit. \n",
"\n",
"For certain operation $A$, $a$ is called the corresponding quantum amplitude. Quantum amplitude estimation is to estimate the value of the unknown parameter $a$.\n",
"\n",
"In the noisy intermediate-scale quantum (NISQ) [2] era, in order to implement quantum algorithms on hardwares, researchers persue for shallower quantum circuit and more concise quantum operations. In this tutorial, we use quantum query complexity $N_{query}$ to compare the performance of different estimation algorithms. Quantum query complexity is defined as the total number of implementing operation $A$ within the whole quantum circuit. Generally, we hope to achieve higher estimation accuracy with lower quantum query complexity.\n",
"\n",
"Three algorithms are going to be reviewed in this tutorial, which are classical Monte-Carlo method (CMC), maximum likelihood amplitude estimation(MLAE) [3], and variational quantum amplitude estimation. The tutorial shows MLAE provides better estimation accuracy than CMC with the same quantum query complexity. VQAE is developed based on MLAE and requires much shallower quantum circuit than MLAE."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"# Import related packages\n",
"import paddle\n",
"from paddle_quantum.gate import Oracle\n",
"from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.state import State, zero_state\n",
"from paddle_quantum.loss import StateFidelity\n",
"import numpy as np\n",
"from numpy import pi, log\n",
"from scipy import optimize\n",
"import warnings\n",
"warnings.filterwarnings('ignore')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For the general case of $n+1$ qubits, $| \\psi_{good}\\rangle_{n}$ and $| 1 \\rangle_{n}$ are an pair of orthonormal basis reconstructed by the $2^n$ basis expanded by $n$ qubits in the Hilbert space.\n",
"\n",
"For concision, we consider the case of single qubit in the following tutorial. We defined the two basis $| \\psi_{bad} \\rangle$ and $| 0 \\rangle$ as bad state and good state, $A | 1 \\rangle_{1+1}=| 0 \\rangle_{1+1}=\\sqrt{a}| \\chi_0 \\rangle| 1 \\rangle_{anc}+\\sqrt{1-a}| 1 \\rangle| 0 \\rangle_{anc}$. Moreover, we can neglect the ancillary qubit when implementing on Paddle Quantum, $A | 0 \\rangle_{1}=| 0 \\rangle_{1}=\\sqrt{a}| \\chi_0 \\rangle+\\sqrt{1-a}| 1 \\rangle$.\n",
"\n",
"Intuitively, single-qubit quantum state can be demonstrated as a vector from the center to a point on the surface of a Bloch sphere. In this case, the effect of unitary operator $A$ can be regarded as a rotation of the vector on the Bloch sphere.\n",
"\n",
"Thus, we can use a general single-qubit rotation gate $U3$ to construct the unitary operator $A$."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The quantum amplitude correspond to the unitary operator A is 0.25\n"
]
}
],
"source": [
"# Define the unitary operator A\n",
"n_qubits = 1 # Single qubit\n",
"amp_operator = Circuit(n_qubits) # Construct the single-qubit unitary operator A\n",
"set_param = pi/3 # The parameter for the unitary operator A\n",
"amp_param = paddle.to_tensor([set_param, 0, 0], dtype='float32') # Transfer the parameter to tensor\n",
"amp_operator.u3(param=amp_param) # Input the parameter to a U3 gate to construct A\n",
"\n",
"initial_state = zero_state(n_qubits) # Define the initial state as zero state\n",
"chi_0_state =amp_operator(initial_state) # The effect of applying A on zero state\n",
"quantum_amp =chi_0_state.measure()['1'] # Output the pre-set quantum amplitude for estimation\n",
"print(f\"The quantum amplitude correspond to the unitary operator A is {quantum_amp}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Brief introduction to QAE"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In 2002, Brassard et al. [4] proposed a scheme for quantum amplitude estimation which showed quadratic quantum speedup compared with CMC. For certain estimation error $\\epsilon$, the quantum query complexity are proved as:\n",
"\n",
"$$\n",
" N_{q-AE}\\sim O(1/\\epsilon)\\quad N_{q-MC}\\sim O(1/\\epsilon^2)\n",
"$$\n",
"\n",
"The key process of traditional quantum amplitude estimation is quantum amplitude amplification (QAA). QAA is generalized from Grover's search algorithm.\n",
"\n",
"Assume a amplification operator $Q$ defined as:\n",
"\n",
"$$\n",
"Q=-R_{\\chi}R_{good},\n",
"$$\n",
"\n",
"where\n",
"\n",
"$$\n",
"R_{\\chi}=I-2| 1 \\rangle_{n+1}\\langle{\\chi_0}|_{n+1}=I-2A| \\psi_{bad} \\rangle_{n+1} \\langle 0|_{n+1}A^\\dagger\n",
"$$\n",
"\n",
"$$\n",
"R_{good}=I-2| 0 \\rangle_{n} \\langle 1| \\otimes | 1 \\rangle \\langle \\psi_{good}|_{n}\n",
"$$\n",
"\n",
"$R_{\\chi}$ and $R_{good}$ are reflection operators in the 2-dimensional subspace $H_{\\chi}$ expanded by basis $| 0 \\rangle| \\chi_0 \\rangle$ and $| 1 \\rangle| 1 \\rangle$ .\n",
"\n",
"We can consider $| 0 \\rangle$ as a vector in subspace $H_{\\chi}$. \n",
"\n",
"$$\n",
"| 0 \\rangle_{n+1}=\\cos(\\theta)\n",
"| 0 \\rangle_{n}| \\chi_0 \\rangle+\\sin(\\theta)| 1 \\rangle_{n}| 0 \\rangle,\n",
"$$\n",
"\n",
"where $\\theta$ is the angle between vector $| \\chi_0 \\rangle$ and the axis of $| 0 \\rangle| \\psi_{good} \\rangle$. We can define the quantum amplitude as $a=\\sin^2(\\theta)$.\n",
"\n",
"If we apply amplification operator $Q$ on $| 1 \\rangle$ for $m$ times, we can prove that:\n",
"\n",
"$$\n",
"| \\psi_{good} \\rangle_{n+1}=Q^m| 1 \\rangle_{n+1}=\\cos[(2m+1)\\theta]\n",
"| \\psi_{bad} \\rangle_{n}| 0 \\rangle+\\sin[(2m+1)\\theta]| \\chi_0 \\rangle_{n}| \\chi_0 \\rangle\n",
"$$\n",
"\n",
"Intuitively, in 2-dimensional subspace $H_{\\chi}$, the effect of amplification operator $Q$ can be regarded as a $2\\theta$ rotation of vector $| \\psi_{bad}\\rangle$ towards the axis of $| 0 \\rangle| \\psi_{good} \\rangle$. \n",
"\n",
"Also, if we apply the amplification operator $Q$ for certain times $m=\\lfloor \\frac{1}{2}(\\frac{\\pi}{2\\theta}-1)\\rfloor$, the quantum amplitude of the rotated state will approximate one $a_m=\\sin[(2m+1)\\theta]\\approx1$."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"# Construct the reflection operator R_chi\n",
"identity = paddle.to_tensor([[1, 0],[0, 1]], dtype=\"complex64\") # Define the identity matrix\n",
"density_matrix_0 = chi_0_state.ket @ chi_0_state.bra # amp_0_state in the form of density matrix\n",
"r_chi = identity - paddle.to_tensor([2], dtype=\"complex64\") * density_matrix_0 # Construct the reflection operator respect to state chi"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"# Construct the reflection operator R_good\n",
"flip = Circuit(n_qubits)\n",
"flip.x() \n",
"one_state = flip(initial_state) # Construct the \"one state\" which is defined as good state\n",
"density_matrix_good = one_state.ket @ one_state.bra # Good state in the form of density matrix\n",
"r_good = identity - paddle.to_tensor([2], dtype=\"complex64\") * density_matrix_good # Construct the reflection operator respect to the good state"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"# Construct the amplification operator Q\n",
"num_amplification = 1 # The total times of apply Q\n",
"Q_operator = Oracle(paddle.to_tensor([-1], dtype=\"complex64\") * r_chi @ r_good, qubits_idx=0, num_qubits=1, depth=num_amplification) # Use Oracle to construct the amplification operator"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"From the definition $a = \\sin^2(\\theta)$, estimating quantum amplitude $a$ can be done by estimating $\\theta$. We notice that the amplification operator can be written as $Q=-R_{\\chi}R_{good}=e^{-i2\\theta Y}$, where $| 1 \\rangle_{n+1}$ is the eigenvector of $Q$ with eigenvalue $e^{\\pm2i\\theta}$. Thus, we can apply quantum phase estimation on $Q$ to estimate $\\theta$. The circuit for quantum phase estimation is shown as follow."
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "76f5e510",
"metadata": {},
"source": [
"![QPE](./figures/VQAE-fig-qpe.png \"Figure 1: Quantum circuit for quantum amplitude estimation [4].\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, the circuit of quantum phase estimation includes quantum fourier transform and multiple control gate, which means it's very difficult to implement quantum amplitude estimation on NISQ devices. Therefore, researcher have been trying to design better algorithms which have lower requirements on the hardware, in order to implement this algorithm and demonstrate quantum advantages."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The topic of this tutorial, VQAE, is a recently proposed algorithm. VQAE is developed based on the framework of maximum likelihood amplitude estimation by adding a variational approximation process which keeps the quantum circuit within a certain depth. Generally, shallower quantum circuits are easier to be implemented. Furthermore, compared with CMC, VQAE and MLAE can achieve the same accuracy with lower quantum query complexity. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the following, the tutorial will show how to implement CMC, MLAE, and VQAE based on Paddle Quantum."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Classical Monte-Carlo algorithm (CMC)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The idea of CMC is quite straightforward and purely classical. We prepare the state in $A| 1 \\rangle_{n+1}=\\sqrt{a}| \\psi_{bad} \\rangle_{n}| 0 \\rangle_{anc}+\\sqrt{1-a}| 1 \\rangle_{n}| 0 \\rangle_{anc}$ then make measurements on an entangled ancillary qubit repeatedly. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The frequency of measuring the ancillary qubit as $| 1 \\rangle$ estimates the quantum amplitude $a$.\n",
"\n",
"$$\n",
" a \\approx \\frac{N_{good}}{N_{measure}}\n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then we use Paddle Quantum to implement CMC. Consider the case of single-qubit, we can choose not to introduce the ancillary qubit."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "d8c88075",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CMC Estimation result is a = 0.24915, absolute error is 0.0008499999999999897, relative error is 0.33999999999999586%\n",
"CMC Query complexity is 20000\n"
]
}
],
"source": [
"# Classical Monte-Carlo method\n",
"N_sample_mc = 20000 # The total amount of sampling\n",
"N_good = 0 # Initialize the times of measuring good state\n",
"for i in range(N_sample_mc):\n",
" result = chi_0_state.measure(shots=1) # Sample the chi state once\n",
" N_good += result['1'] # If the measurement result is \"1\", N_good plus one\n",
"amp_mc = N_good/N_sample_mc # Estimate the quantum amplitude by the probability of mearsuring \"1\"\n",
"error_mc = abs(amp_mc - quantum_amp) # Absolute error of the estimation \n",
"rate_mc = error_mc/quantum_amp # Relative error of the estimation\n",
"print(f\"CMC Estimation result is a = {amp_mc}, absolute error is {error_mc}, relative error is {100*rate_mc}%\")\n",
"print(f\"CMC Query complexity is {N_sample_mc}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"CMC can achieve certain estimation accuracy. Since the estimation is based on frequency, the accuracy of estimation is determined by the amount of measurements $N_{measure}$. Larger amount of measurement gives higher accuracy. However, we notice that each measurement requires implementing unitary $A$ once to construct $| 1 \\rangle$. Thus, the query complexity of CMC equals to the total amount of sampling. It's quite costly to achieve the desired accuracy, especially for small unknown amplitude a. As will be shown later, quantum algorithms can achieve better accuracy with same query complexity, which is called quantum speedup."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Maximum likelihood amplitude estimation(MLAE)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"MLAE provides a way to implement amplitude estimation without quantum phase estimation. \n",
"\n",
"The idea of MLAE is to combine the results of several quantum circuits together through likelihood function, then estimate $\\theta$ by optimizing the likelihood function. It's explained step by step as follows."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Step 1:** \n",
"\n",
"* Give a set $\\{m_k\\}$ of $M$ terms. Each term of the set $m_k$ determines how many times amplification operator $Q$ is applied to $| 1 \\rangle$, i.e. $Q^{m_k}| \\psi_{bad} \\rangle$. \n",
"\n",
"* Prepare state $Q^{m_k}| 0 \\rangle$ for $N_k$ times and make good/bad state measurement on the ancillary qubit for all of them. $h_k$ denotes how many times measurement gives good state.\n",
"\n",
"* Write the likelihood function for term $m_k$ as:\n",
"\n",
"$$\n",
" L_k(h_k;\\theta_a)=[\\sin^2((2m_k+1)\\theta_a)]^{h_k}[\\cos^2((2m_k+1)\\theta_a)]^{N_k-h_k}\n",
"$$\n",
"\n",
"**Step 2:**\n",
"\n",
"Combine the likelihood functions $L_k(h_k;\\theta_a)$ for $\\{m_0,......,m_M\\}$ as a single likelihood function as:\n",
"\n",
"$$\n",
" L(\\mathbf{h};\\theta_a)= \\prod^{M}_{k=0}L_k(h_k;\\theta_a)\n",
"$$\n",
"\n",
"Where $\\mathbf{h}=(h_0,h_1,...,h_M)$\n",
"\n",
"**Step 3:**\n",
"\n",
"Find the $\\theta_a$ that maximizes the single likelihood function $L(\\mathbf{h};\\theta_a)$\n",
"\n",
"$$\n",
" \\hat{\\theta_a}=\\arg{\\max_{\\theta_a}{L(\\mathbf{h};\\theta_a)}}=\\arg{\\max_{\\theta_a}{\\text{ln}[L(\\mathbf{h};\\theta_a)]}}\n",
"$$\n",
"\n",
"Then the final result of MLAE can be given by $\\hat{a}=\\sin^2\\hat{\\theta_a}$. And the process of MLAE is shown as figure below."
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "6dcf46cc",
"metadata": {},
"source": [
"![MLAE](./figures/VQAE-fig-mlae.png \"Figure 2: Process of MLAE [3].\")"
]
},
{
"cell_type": "markdown",
"id": "b34937f2",
"metadata": {},
"source": [
"By choosing proper set $\\{m_k\\}$ and parameter $N_k$, estimation result can show quantum advantage. There are two main ways to give $\\{m_k\\}$.\n",
"\n",
"1. Linearly incremental sequence, LIS: $N_k = N_{shot}, \\forall k$,and $m_k = k$. i.e.$m_0=0, m_1=1, ...,m_M=M$\n",
" \n",
" When $M\\gg1$, the estimation error is $\\hat{\\epsilon}\\sim N_q^{-3/4}$ which shows quantum advantage compared to CMC.\n",
" \n",
"2. Exponentially incremental sequence, ELS: $N_k = N_{shot}, \\forall k$,and $m_k = 2^{k-1}$。i.e. $m_0=0, m_1=2^0, ..., m_M=2^{M-1}$\n",
" \n",
" Estimation error: $\\hat{\\epsilon}\\sim N_q^{-1}$\n",
" \n",
"In VQAE, set $\\{m_k\\}$ is given by LIS."
]
},
{
"cell_type": "markdown",
"id": "7673d10a",
"metadata": {},
"source": [
"### MLAE by Paddle Quantum"
]
},
{
"cell_type": "markdown",
"id": "25488db9",
"metadata": {},
"source": [
"In the following, we implement MLAE based on Paddle Quantum."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# Step 1 of MLAE\n",
"\n",
"# Initialize parameters \n",
"M = 25 # Maximum times of implementing Q\n",
"m = np.arange(0, M, 1) #LIS\n",
"N_k = 25 # Total amount of sampling\n",
"h_k = np.zeros(M) # Initialize the data space to store the times of measuring good state.\n",
"\n",
"# Sampling process\n",
"for k in range(M):\n",
" Q_operator_MLAE = Oracle(paddle.to_tensor([-1], dtype=\"complex64\") * r_chi @ r_good, qubits_idx=0, num_qubits=1, depth=k)\n",
" for i in range(N_k):\n",
" rotated_state = Q_operator_MLAE(chi_0_state) # Implement amplification operator on initial state\n",
" result = rotated_state.measure(shots = 1) # Measure and record the number of measuring good state\n",
" h_k[k] += result['1'] # Store the number of good state for different \"k\""
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"# Step 2 of MLAE \n",
"# Definition of the likelihood function (in logarithmic form)\n",
"params = ()\n",
"def likelihood(z, *params):\n",
" theta = z\n",
" f = 0\n",
" for i in range(M):\n",
" f = f + log(np.sin((2 * m[i] + 1) * theta) ** (2 * h_k[i]) * np.cos((2 * m[i] + 1) * theta) ** (2 * (N_k - h_k[i])))\n",
" return (-f) # the following algorithm will find the minimum, minus sign corresponds the result to maximum."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The maximum value for likelihood function is -243.068172605532 when theta = 0.5233743030763384 rad\n",
"MLAE result is 0.249805626294017, absolute error is 0.00019437370598299197, relative error is 0.07774948239319679%\n"
]
}
],
"source": [
"# Step 3 of MLAE\n",
"# Use Brute-force search algorithm to find the maximum value of likelihood function\n",
"rranges = (0, pi/2, pi/200) # Range of grid for Brute-force algorithm\n",
"resbrute = optimize.brute(likelihood, (rranges,), args=params, full_output=True, finish=optimize.fmin) # Call Brute-force algorithm\n",
"theta_a = resbrute[0][0] # Theta corresponds to the minimum of -f\n",
"amp_MLAE = np.sin(theta_a) ** 2 # Quantum amplitude estimation from MLAE\n",
"error_MLAE = abs(amp_MLAE - quantum_amp) # Absolute estimation error\n",
"rate_MLAE = error_MLAE/quantum_amp # Relative estimation error\n",
"print(f\"The maximum value for likelihood function is {-resbrute[1]} when theta = {resbrute[0][0]} rad\")\n",
"print(f\"MLAE result is {amp_MLAE}, absolute error is {error_MLAE}, relative error is {100 * rate_MLAE}%\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Based on the above algorithm, the quantum query complexity of MLAE can be written as:\n",
"\n",
"$$\n",
"N_{q-MLAE}=\\sum^{M}_{k=0}N_k(2m_k+1)\n",
"$$\n",
"\n",
"Where $2m_k$ denotes implementing amplification operator $Q$ by $m_k$ times and each implementation requires to call $A$ and $A^\\dagger$ once.($Q = -(I-2A| 0 \\rangle_{n+1}\\langle{0}|_{n+1}A^\\dagger)(I-2| \\psi_{bad}\\rangle_{n}\\langle{\\psi_{good}}|_{n}\\otimes | 1 \\rangle\\langle{1}|) $)\n",
"\n",
"\"+1\" origins from preparing the initial state $| 1 \\rangle_{n+1}$. ($| 0 \\rangle_{n+1}\n",
"=A| \\chi_0 \\rangle_{n+1}$); $N_k$ denotes the sampling amount for the $k$ th item. "
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The quantum query complexity for MLAE is 15625\n"
]
}
],
"source": [
"# Quantum query complexity for MLAE \n",
"N_q_MLAE = 0\n",
"for i in range(M):\n",
" N_q_MLAE += N_k * (2 * i + 1)\n",
"print(f\"The quantum query complexity for MLAE is {N_q_MLAE}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Comparison between MLAE and CMC"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For the amplitude estimation problem of the same unitary operator $A$, we compare the relation of estimation error $\\epsilon$ and query complexity $N_q$ between the two algorithm. For example, setting the input parameter of the unitary A as $\\pi/8$, the numerical experiment result of two algorithms with same query complexity is shown as below. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![comparison](./figures/VQAE-fig-comparison.png \"Figure 3: Performance comparison between MLAE and CMC.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The above figure shows the estimation error of MLAE is about one order smaller than that of CMC. The result shows the advantage of MLAE compared to CMC. The advantage will be more evident as the preset quantum amplitude decreases.\n",
"\n",
"With the provided codes, readers are encouraged to do numerical experiments for verification. "
]
},
{
"cell_type": "markdown",
"id": "4b86a83e",
"metadata": {},
"source": [
"## Variational quantum amplitude estimation"
]
},
{
"cell_type": "markdown",
"id": "7bab7472",
"metadata": {},
"source": [
"In MLAE, we notice that the depth of quantum circuits grows as $2m+1$. Although we get rid of quantum phase estimation in MLAE, large quantum circuit depth will also make implementation difficult. \n",
"\n",
"The recent work [1] proposed amplitude estimation with the help of constant-depth quantum circuits that variationally approximate states during amplitude amplification.\n",
"\n",
"The main different between VQAE and MLAE is the variational approximation process. During quantum amplitude amplification (Step 1 of MLAE), the state $| 1 \\rangle_{n+1}$ is periodically replaced by a variational quantum state. For linear increment sequence in MLAE, we choose an positive integer $k$ as variational period, variational approximation begins when it reaches $Q^k\\ (0<k<M)$."
]
},
{
"cell_type": "markdown",
"id": "6dadbc7e",
"metadata": {},
"source": [
"### Pseudo code for VQAE"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![pseudo](./figures/VQAE-fig-pesudo.png \"Figure 4: Pseudo code for VQAE.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Figure 4: Pseudo code for VQAE"
]
},
{
"cell_type": "markdown",
"id": "4f167e02",
"metadata": {},
"source": [
"Based on $\\{h_m\\}$, we can calculate the single likelihood functions $L_m(h_m;\\theta_a)$ and continue to follow the process of MLAE. \n",
"\n",
"From the same process as MLAE, the estimation result can be written as:\n",
"\n",
"$$\n",
"\\hat{\\theta_a}\n",
"=\\arg{\\max_{\\theta_a}{\\text{ln}[L(\\{h_m\\};\\theta_a)]}}\n",
"$$"
]
},
{
"cell_type": "markdown",
"id": "2832b3c5",
"metadata": {},
"source": [
"### Variational approximation process"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Variational approximation process is the key part of VQAE algorithm. In this process, assume the variational period is $k$, target state $Q^k | 1 \\rangle_{n+1}$ of circuit depth $2k+1$ is approximated and replace by variational quantum state $| \\psi_{bad \\rangle(\\mathbf{\\lambda})}$ of circuit depth 1, where $\\mathbf{\\lambda}$ denotes variational parameters.\n",
"\n",
"Better variational approximation indicates higher state fidelity between the target state and the variational quantum state. The state fidelity between the two states can be written as:\n",
"\n",
"$$\n",
" F(\\lambda)=Re[\\langle{\\phi_{var}(\\mathbf{\\lambda})}|_{n+1}Q^k | 0 \\rangle_{n+1}]\n",
"$$\n",
"\n",
"The aim of variational approximation is to find the set of parameters $\\mathbf{\\lambda}$ that maximizes the state fidelity $F(\\mathbf{\\lambda})$. The optimal parameters can be written as:\n",
"\n",
"$$\n",
" | 1 \\rangle_{n+1}=| 0 (\\tilde{\\mathbf{\\lambda}}) \\rangle_{n+1}\n",
" \\quad\n",
" \\tilde{\\mathbf{\\lambda}}=\\arg{\\max_{\\mathbf{\\lambda}}{F(\\mathbf{\\lambda})}}\n",
"$$\n",
"\n",
"In practice, we define the infidelity ($\\text{Infidelity}(\\lambda) = 1 - F(\\lambda)$) as the loss function and use Adam optimizer to find the paramters that minimize the loss function. Notice that the depth of circuit to calculate the loss function is $2k+2$, which is the deepest part in the whole VQAE circuit.\n",
"\n",
"Generally, the parameterized quantum circuit (PQC) for variational quantum state can be written as:\n",
"\n",
"$$\n",
"\\left|\\phi_{\\text {var }}(\\boldsymbol{\\lambda})\\right\\rangle_{n+1}=\\mathcal{U}_{\\text {var }}(\\boldsymbol{\\lambda})\\left|\\phi_{\\text {init }}\\right\\rangle_{n+1}\n",
"=\\prod_{j} e^{-\\mathrm{i} \\lambda_{j} G_{j}}\\left|\\phi_{\\text {init }}\\right\\rangle_{n+1}\n",
"$$\n",
"\n",
"In the following code, variational quantum state is a single-qubit parameterized quantum circuit, each layer is formed by single-qubit rotation gate $R_y(\\theta)$ and $R_z(\\theta)$. The initial state for variational quantum state is $| \\chi_0 \\rangle_{n+1}=| 1 \\rangle_{n+1}$. The variational period is 5.\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "6cfdae51",
"metadata": {},
"source": [
"### VQAE implementation by Paddle Quantum"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"# Define related functions for VQAE\n",
"\n",
"def construct_Q(input_param: float, n_qubits: int, num_amplification):\n",
" r\"\"\" Construct amplification operator Q.\n",
" \n",
" Args:\n",
" input_param: input parameters for U3 rotation gate\n",
" n_qubits: number of qubits in circuit\n",
" num_amplification: number of implementing amplification operator\n",
" \n",
" Returns:\n",
" Oracle: amplification operator with input parameter and number of implementation\n",
" \n",
" \"\"\"\n",
" # Define unitary operator A\n",
" amp_operator = Circuit(n_qubits) \n",
" amp_param = paddle.to_tensor([input_param, 0, 0], dtype='float32') # Preset parameter for unitary operator\n",
" amp_operator.u3(param=amp_param)\n",
"\n",
" initial_state = zero_state(n_qubits) # Define the initial state as zero state\n",
" chi_0_state =amp_operator(initial_state) # Apply unitary operator A on zero state \n",
"\n",
" # Construct reflection operator R_chi\n",
" identity = paddle.to_tensor([[1, 0],[0, 1]], dtype=\"complex64\") # Define identity operator\n",
" density_matrix_0 = chi_0_state.ket @ chi_0_state.bra # Density matrix for amp_0_state\n",
" r_chi = identity - paddle.to_tensor([2], dtype=\"complex64\") * density_matrix_0\n",
"\n",
" # Construct reflection operator R_good\n",
" flip = Circuit(n_qubits)\n",
" flip.x()\n",
" one_state = flip(initial_state) # Construct \"one state\" which is defined as good state\n",
" density_matrix_good = one_state.ket @ one_state.bra # Density matrix for good state\n",
" r_good = identity - paddle.to_tensor([2], dtype=\"complex64\") * density_matrix_good\n",
" \n",
" # Return the amplification operator Q\n",
" return Oracle(paddle.to_tensor(paddle.to_tensor([-1], dtype=\"complex64\") * r_chi @ r_good), qubits_idx=0, num_qubits=n_qubits, depth=num_amplification)\n",
"\n",
"\n",
"def loss_fcn(input_state: State, period: int, start_state: State, target_param: float) -> paddle.Tensor:\n",
" r\"\"\" Calculate the loss function for neural network.\n",
" \n",
" Args:\n",
" input_state: input state\n",
" period: period of variational process \n",
" start_state: current variational quantum state, for constructing target state\n",
" target_param: parameter of target state\n",
" \n",
" Returns:\n",
" loss: the value of loss function which is defined as the infidelity between the input state and target state\n",
" \n",
" \"\"\"\n",
"\n",
" Q_var = construct_Q(target_param, int(1), period)\n",
" Target_state = Q_var(start_state) \n",
" loss = 1 - StateFidelity(Target_state)(input_state)\n",
" return loss\n",
"\n",
"\n",
"def cir_constructor(depth: int) -> Circuit:\n",
" r\"\"\" Construct variational quantum circuit\n",
" \n",
" Args:\n",
" depth: the depth of quantum circuit\n",
" \n",
" Returns:\n",
" variational quantum circuit\n",
" \n",
" Note:\n",
" For single qubit, one layer is defined as a combination of Ry and Rz rotation gate.\n",
" The rotation angles are the parameters of quantum circuit.\n",
" \n",
" \"\"\"\n",
" cir = Circuit(1)\n",
" \n",
" for _ in range(depth):\n",
" cir.ry()\n",
" cir.rz()\n",
" return cir"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"# Initialization of parameters (Notice: VQAE is based on the framework of MLAE)\n",
"M = 25 # Maximum times of implementing Q\n",
"m = np.arange(0, M, 1) # Linear increment sequence\n",
"N_k_vqae = 50 # Total amount of sampling"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<paddle.fluid.core_avx.Generator at 0x1fb5ba83d30>"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Superparameters for variational approximation\n",
"theta_size = 6 # Amount of variational parameters (depends on the depth of circuit)\n",
"ITR = 500 # Number of iterations\n",
"LR = 0.001 # Learning rate \n",
"SEED = 1 # Random number seed\n",
"paddle.seed(SEED)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=================================\n",
"The 1 th variational approximation\n",
"The minimum of loss function 0.011207759380340576\n",
"Optimal variational parameters [-0.39857474 4.4845576 3.8463612 0.5077383 2.1137552 5.696977 ]\n",
"The state fidelity between variational quantum state and target state is 0.9888777136802673\n",
"=================================\n",
"The 2 th variational approximation\n",
"The minimum of loss function 0.02215433120727539\n",
"Optimal variational parameters [3.538579 3.6868277 3.778493 0.8545991 3.3457727 5.151529 ]\n",
"The state fidelity between variational quantum state and target state is 0.9780022501945496\n",
"=================================\n",
"The 3 th variational approximation\n",
"The minimum of loss function 0.12489765882492065\n",
"Optimal variational parameters [2.9717562 4.5942483 3.8417864 1.0915955 2.9404604 4.45632 ]\n",
"The state fidelity between variational quantum state and target state is 0.8756692409515381\n",
"=================================\n",
"The 4 th variational approximation\n",
"The minimum of loss function 0.0724669098854065\n",
"Optimal variational parameters [5.293609 5.0283127 1.7944657 3.575252 2.10594 4.743199 ]\n",
"The state fidelity between variational quantum state and target state is 0.9278923273086548\n",
"=================================\n",
"The 5 th variational approximation\n",
"The minimum of loss function 0.007037222385406494\n",
"Optimal variational parameters [ 2.9615376 6.2987323 2.9560285 -0.05320466 1.5106332 3.6751769 ]\n",
"The state fidelity between variational quantum state and target state is 0.9930309057235718\n"
]
}
],
"source": [
"# Sampling and variational approximation process\n",
"start_state = chi_0_state #start_state is the variational quantum state, equals to chi_0_state in the beginning\n",
"cycle = 5 # Variational period\n",
"h_k_vqae = np.zeros(M) # Initialize the data space to store the times of measuring good state.\n",
"for k in range(M):\n",
" i = np.floor(k / cycle)\n",
" j = k % cycle\n",
" Q_operator_VQAE = construct_Q(set_param, int(1), j)\n",
" for sample in range(N_k_vqae):\n",
" rotated_state = Q_operator_VQAE(start_state) # Implement amplification operator on initial state\n",
" result = rotated_state.measure(shots = 1) # Measure and record the number of measuring good state\n",
" h_k_vqae[k] += result['1'] # Store the number of good state for different \"k\"\n",
"\n",
" if j == cycle - 1: # Condition to trigger variational approximation process\n",
" # Create data space to store loss function\n",
" loss_list = []\n",
"\n",
" # Define the variational circuit\n",
" cir = cir_constructor(3)\n",
"\n",
" # Use Adam optimizer\n",
" opt = paddle.optimizer.Adam(learning_rate=LR, parameters=cir.parameters())\n",
" \n",
" # Optimization iterations\n",
" for itr in range(ITR):\n",
" # Forward optimize the loss function\n",
" loss = loss_fcn(cir(chi_0_state), cycle, start_state, set_param)\n",
"\n",
" # Backward optimize the loss function\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
"\n",
" # Record the loss function (learning curve)\n",
" loss_list.append(loss.item())\n",
" \n",
" print(\"=================================\")\n",
" print(f'The {int(i + 1)} th variational approximation')\n",
" print(f'The minimum of loss function {loss_list[-1]}')\n",
" print(f\"Optimal variational parameters {cir.param.numpy()}\")\n",
" \n",
" Q_test = construct_Q(set_param, int(1), cycle)\n",
" test_state = Q_test(start_state) # Update the test state for fidelity calculation\n",
" \n",
" start_state = cir(chi_0_state) # Update the variational quantum state \n",
" start_state.data.stop_gradient = True \n",
" print(f\"The state fidelity between variational quantum state and target state is {StateFidelity(test_state)(start_state).numpy()[0]}\")"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"# Definition of the likelihood function (in logarithmic form)\n",
"params = ()\n",
"def likelihood_vqae(z, *params):\n",
" theta = z\n",
" f = 0\n",
" for i in range(M):\n",
" f = f + log(np.sin((2 * m[i] + 1) * theta) ** (2 * h_k_vqae[i]) * np.cos((2 * m[i] + 1) * theta) ** (2 * (N_k_vqae - h_k_vqae[i])))\n",
" return (-f)# the following algorithm will find the minimum, minus sign corresponds the result to maximum."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=================================\n",
"The maximum value for likelihood function is -728.8282960799222 when theta = 0.5310066244864633 rad\n",
"VQAE result is 0.2564425882363815, absolute error is 0.006442588236381497, relative error is 2.5770352945525987%\n"
]
}
],
"source": [
"# Step 3 of MLAE\n",
"# Use Brute-force search algorithm to find the maximum value of likelihood function\n",
"rranges = (0, pi/2, pi/200) # Range of grid for Brute-force algorithm\n",
"resbrute = optimize.brute(likelihood_vqae, (rranges,), args=params, full_output=True, finish=optimize.fmin) # Call Brute-force algorithm\n",
"theta_a = resbrute[0][0] # Theta corresponds to the minimum of -f\n",
"amp_VQAE = np.sin(theta_a) ** 2 # Quantum amplitude estimation from VQAE\n",
"error_VQAE = abs(amp_VQAE - quantum_amp) # Absolute estimation error\n",
"rate_VQAE = error_VQAE/quantum_amp # Relative estimation error\n",
"print(\"=================================\")\n",
"print(f\"The maximum value for likelihood function is {-resbrute[1]} when theta = {resbrute[0][0]} rad\")\n",
"print(f\"VQAE result is {amp_VQAE}, absolute error is {error_VQAE}, relative error is {100 * rate_VQAE}%\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As shown above, VQAE algorithm not only achieves amplitude estimation with high accuracy, but also has evidently shallower quantum circuit compared with original MLAE algorithm. The depth of quantum circuit in VQAE is defined by the sequence of MLAE and the variational period. For example, for a circuit with $M = 50$ and variational period of 5. The depth of VQAE is about $\\sim 1/10$ of MLAE. In this way, VQAE can be more promising to be implemented on NISQ hardware compared to MLAE. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The quantum query complexity of VQAE algorithm consists of sampling complexity $N_{samp}$ and variational complexity $N_{var}$.\n",
"$$\n",
"N = N_{var} + N_{samp},\n",
"$$\n",
"$$\n",
"N_{var} = N_{var/1}(2k+2)\\lfloor M/k \\rfloor \\sim O(k\\lfloor M/k \\rfloor),\n",
"$$\n",
"$$\n",
"N_{samp} = hk(k+2)\\lfloor M/k \\rfloor + h(M\\%k)(M\\%k+2),\n",
"$$\n",
"where $M$ is the maximum of linear increment sequence, $k$ is the variational period of VQAE, $h$ is the number of repeated sampling for each state. $N_{var/1}=2n_fn_sn_p$ is the number of circuits needed to be run for each variational update, where $n_p$ is the number of parameters of a parameterized quantum circuit (PQC), $n_s$ is the total number of sweeps through all the parameters of the PQC, and $n_f$ is the number of Bernoulli trials per evaluation of the objective function ($\\sim n_s$). The factor 2 comes from the fact that two evaluations of the objective function are required for each evaluation of the gradient. For detailed explanation, please refer to [1]."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Sampling complexity is 8750\n",
"Variational complexity is 180000000\n",
"Quantum query complexity for VQAE is 180008750\n"
]
}
],
"source": [
"# Calculate the quantum query complexity of VQAE\n",
"from math import floor\n",
"M_vqae = M\n",
"k_cycle = cycle\n",
"n_p = theta_size\n",
"n_s = ITR\n",
"n_f = n_s\n",
"N_var1 = 2 * n_p * n_s * n_f\n",
"N_var = N_var1 * (2 * k_cycle + 2) * floor(M_vqae/k_cycle)\n",
"N_samp = N_k_vqae * k_cycle * (k_cycle + 2) * floor(M_vqae/k_cycle) + N_k_vqae * (M_vqae % k_cycle) * (M_vqae % k_cycle + 2)\n",
"print(f\"Sampling complexity is {N_samp}\")\n",
"print(f\"Variational complexity is {N_var}\")\n",
"print(f\"Quantum query complexity for VQAE is {N_var + N_samp}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We notice the quantum query complexity of VQAE is large, and mainly contributed from the complexity of variational approximation process. There is a trade-off between complexity from the depth of quantum circuit and variatonal process. However, there exists some design of the variational process to decrease the variational complexity. For example, adaptive VQAE mentioned in [1]. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The tutorial first reviews the quantum amplitude estimation problem and its difficulty. Then maximum likelihood amplitude estimation(MLAE), which avoids quantum phase estimation, is reviewed. However, the circuit of MLAE can be too deep to be implemented by NISQ hardware. To decrease the depth of circuit of MLAE, variational quantum amplitude estimation (VQAE) is developed based on MLAE. The variational approximation process in VQAE keeps the depth below a constant number. Therefore, VQAE increases the probability of realizing quantum amplitude estimation on NISQ hardware. Also, it's necessary to notice that variational process will introduce variational complexity. Thus, there is a tradeoff between variational process and the depth of quantum circuit.\n",
"\n",
"The tutorial implements single-qubit MLAE and VQAE based on Paddle Quantum, and demonstrates high-accuracy estimation of the preset quantum amplitude. Furthermore, the tutorial compares the relation of estimation error and quantum query complexity between MLAE and Classical Monte-Carlo method, and shows the quantum speedup effect by numerical experiment results."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"_______\n",
"## References"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[1] Plekhanov, Kirill, et al. \"Variational quantum amplitude estimation.\" Quantum 6 (2022): 670. https://quantum-journal.org/papers/q-2022-03-17-670/\n",
"\n",
"[2] Preskill, John. \"Quantum computing in the NISQ era and beyond.\" Quantum 2 (2018): 79. https://quantum-journal.org/papers/q-2018-08-06-79/\n",
"\n",
"[3] Suzuki, Yohichi, et al. \"Amplitude estimation without phase estimation.\" Quantum Information Processing 19.2 (2020): 1-17. https://link.springer.com/article/10.1007/s11128-019-2565-2\n",
"\n",
"[4] Brassard, Gilles, et al. \"Quantum amplitude amplification and estimation.\" Contemporary Mathematics 305 (2002): 53-74. https://arxiv.org/pdf/quant-ph/0005055.pdf"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.7.13 ('py3.7_pq2.2.1')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.13"
},
"vscode": {
"interpreter": {
"hash": "4e4e2eb86ad73936e915e7c7629a18a8ca06348106cf3e66676b9578cb1a47dd"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 1,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:55:59.838299Z", "end_time": "2021-04-30T08:55:59.838299Z",
...@@ -83,9 +83,9 @@ ...@@ -83,9 +83,9 @@
"import scipy\n", "import scipy\n",
"import paddle\n", "import paddle\n",
"from numpy import trace as np_trace\n", "from numpy import trace as np_trace\n",
"import paddle_quantum\n", "import paddle_quantum as pq\n",
"from paddle_quantum.ansatz import Circuit\n", "from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.state import zero_state, State\n", "from paddle_quantum.state import zero_state\n",
"from paddle_quantum.qinfo import state_fidelity, partial_trace, pauli_str_to_matrix" "from paddle_quantum.qinfo import state_fidelity, partial_trace, pauli_str_to_matrix"
] ]
}, },
...@@ -125,10 +125,12 @@ ...@@ -125,10 +125,12 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"N = 4 # 量子神经网络的宽度\n", "N = 4 # 量子神经网络的宽度\n",
"N_SYS_B = 3 # 用于生成吉布斯态的子系统B的量子比特数 \n", "N_SYS_B = 3 # 用于生成吉布斯态的子系统B的量子比特数 \n",
"SEED = 16 # 固定随机种子\n", "SEED = 16 # 固定随机种子\n",
"beta = 1.5 # 设置逆温度参数 beta" "beta = 1.5 # 设置逆温度参数 beta\n",
"pq.set_backend('density_matrix') # 设置密度矩阵后端\n",
"pq.set_dtype('complex128') # 设置计算精度"
] ]
}, },
{ {
...@@ -152,8 +154,8 @@ ...@@ -152,8 +154,8 @@
"rho_G = scipy.linalg.expm(-1 * beta * hamiltonian) / np_trace(scipy.linalg.expm(-1 * beta * hamiltonian))\n", "rho_G = scipy.linalg.expm(-1 * beta * hamiltonian) / np_trace(scipy.linalg.expm(-1 * beta * hamiltonian))\n",
"\n", "\n",
"# 设置成 Paddle quantum 所支持的数据类型\n", "# 设置成 Paddle quantum 所支持的数据类型\n",
"hamiltonian = hamiltonian.astype(\"complex64\")\n", "hamiltonian = hamiltonian.astype(\"complex128\")\n",
"rho_G = paddle.to_tensor(rho_G, dtype=\"complex64\")" "rho_G = paddle.to_tensor(rho_G, dtype=\"complex128\")"
] ]
}, },
{ {
...@@ -247,7 +249,7 @@ ...@@ -247,7 +249,7 @@
" rho_AB = cir(zero_state(N))\n", " rho_AB = cir(zero_state(N))\n",
" \n", " \n",
" # 计算偏迹 partial trace 来获得子系统B所处的量子态 rho_B\n", " # 计算偏迹 partial trace 来获得子系统B所处的量子态 rho_B\n",
" rho_B = partial_trace(rho_AB, 2 ** (N - N_SYS_B), 2 ** (N_SYS_B), 1)\n", " rho_B = partial_trace(rho_AB.data, 2 ** (N - N_SYS_B), 2 ** (N_SYS_B), 1)\n",
" \n", " \n",
" # 计算三个子损失函数\n", " # 计算三个子损失函数\n",
" rho_B_squre = rho_B @ rho_B\n", " rho_B_squre = rho_B @ rho_B\n",
...@@ -310,7 +312,7 @@ ...@@ -310,7 +312,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 8,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:56:19.753228Z", "end_time": "2021-04-30T08:56:19.753228Z",
...@@ -322,14 +324,14 @@ ...@@ -322,14 +324,14 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"iter: 10 loss: -3.1084 fid: 0.9241\n", "iter: 10 loss: -3.1085 fid: 0.9241\n",
"iter: 20 loss: -3.3375 fid: 0.9799\n", "iter: 20 loss: -3.3375 fid: 0.9799\n",
"iter: 30 loss: -3.3692 fid: 0.9897\n", "iter: 30 loss: -3.3692 fid: 0.9897\n",
"iter: 40 loss: -3.3990 fid: 0.9929\n", "iter: 40 loss: -3.3990 fid: 0.9929\n",
"iter: 50 loss: -3.4133 fid: 0.9959\n", "iter: 50 loss: -3.4133 fid: 0.9959\n",
"\n", "\n",
"训练后的电路: \n", "训练后的电路: \n",
"--Ry(6.290)----*--------------x----Ry(0.260)--\n", "--Ry(6.290)----*--------------x----Ry(0.747)--\n",
" | | \n", " | | \n",
"--Ry(4.745)----x----*---------|----Ry(6.249)--\n", "--Ry(4.745)----x----*---------|----Ry(6.249)--\n",
" | | \n", " | | \n",
...@@ -342,9 +344,6 @@ ...@@ -342,9 +344,6 @@
], ],
"source": [ "source": [
"paddle.seed(SEED)\n", "paddle.seed(SEED)\n",
"\n",
"# 设置计算模式为密度矩阵模式\n",
"paddle_quantum.set_backend(backend='density_matrix')\n",
" \n", " \n",
"# 我们需要将 Numpy array 转换成 Paddle 中支持的 Tensor\n", "# 我们需要将 Numpy array 转换成 Paddle 中支持的 Tensor\n",
"H = paddle.to_tensor(hamiltonian)\n", "H = paddle.to_tensor(hamiltonian)\n",
...@@ -368,7 +367,7 @@ ...@@ -368,7 +367,7 @@
" opt.clear_grad()\n", " opt.clear_grad()\n",
"\n", "\n",
" # 转换成 Numpy array 用以计算量子态的保真度 F(rho_B, rho_G)\n", " # 转换成 Numpy array 用以计算量子态的保真度 F(rho_B, rho_G)\n",
" fid = state_fidelity(State(rho_B), State(rho_G))\n", " fid = state_fidelity(rho_B, rho_G)\n",
"\n", "\n",
" # 打印训练结果\n", " # 打印训练结果\n",
" if itr % 10 == 0:\n", " if itr % 10 == 0:\n",
...@@ -417,7 +416,7 @@ ...@@ -417,7 +416,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3.7.13 ('py3.7_pq2.2.1')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -431,7 +430,7 @@ ...@@ -431,7 +430,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.13" "version": "3.7.13"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
...@@ -445,6 +444,11 @@ ...@@ -445,6 +444,11 @@
"toc_position": {}, "toc_position": {},
"toc_section_display": true, "toc_section_display": true,
"toc_window_display": false "toc_window_display": false
},
"vscode": {
"interpreter": {
"hash": "4e4e2eb86ad73936e915e7c7629a18a8ca06348106cf3e66676b9578cb1a47dd"
}
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 1,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:56:40.078886Z", "end_time": "2021-04-30T08:56:40.078886Z",
...@@ -80,9 +80,9 @@ ...@@ -80,9 +80,9 @@
"import scipy\n", "import scipy\n",
"import paddle\n", "import paddle\n",
"from numpy import trace as np_trace\n", "from numpy import trace as np_trace\n",
"import paddle_quantum\n", "import paddle_quantum as pq\n",
"from paddle_quantum.ansatz import Circuit\n", "from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.state import zero_state, State\n", "from paddle_quantum.state import zero_state\n",
"from paddle_quantum.qinfo import state_fidelity, partial_trace, pauli_str_to_matrix" "from paddle_quantum.qinfo import state_fidelity, partial_trace, pauli_str_to_matrix"
] ]
}, },
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 2,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:56:40.099967Z", "end_time": "2021-04-30T08:56:40.099967Z",
...@@ -122,15 +122,17 @@ ...@@ -122,15 +122,17 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"N = 4 # The width of the QNN\n", "N = 4 # The width of the QNN\n",
"N_SYS_B = 3 # The number of qubits of subsystem B used to generate the Gibbs state\n", "N_SYS_B = 3 # The number of qubits of subsystem B used to generate the Gibbs state\n",
"SEED = 16 # Fixed random seed\n", "SEED = 16 # Fixed random seed\n",
"beta = 1.5 # Set the inverse temperature parameter beta" "beta = 1.5 # Set the inverse temperature parameter beta\n",
"pq.set_backend('density_matrix') # Set density matrix backend\n",
"pq.set_dtype('complex128') # Set calculation precision"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 3,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:56:40.201757Z", "end_time": "2021-04-30T08:56:40.201757Z",
...@@ -149,8 +151,8 @@ ...@@ -149,8 +151,8 @@
"rho_G = scipy.linalg.expm(-1 * beta * hamiltonian) / np_trace(scipy.linalg.expm(-1 * beta * hamiltonian))\n", "rho_G = scipy.linalg.expm(-1 * beta * hamiltonian) / np_trace(scipy.linalg.expm(-1 * beta * hamiltonian))\n",
"\n", "\n",
"# Set to the data type supported by Paddle Quantum\n", "# Set to the data type supported by Paddle Quantum\n",
"hamiltonian = hamiltonian.astype(\"complex64\")\n", "hamiltonian = hamiltonian.astype('complex128')\n",
"rho_G = paddle.to_tensor(rho_G, dtype=\"complex64\")" "rho_G = paddle.to_tensor(rho_G, dtype='complex128')"
] ]
}, },
{ {
...@@ -177,7 +179,7 @@ ...@@ -177,7 +179,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": 4,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:56:40.213503Z", "end_time": "2021-04-30T08:56:40.213503Z",
...@@ -229,7 +231,7 @@ ...@@ -229,7 +231,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 12, "execution_count": 5,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:56:40.238960Z", "end_time": "2021-04-30T08:56:40.238960Z",
...@@ -244,7 +246,7 @@ ...@@ -244,7 +246,7 @@
" rho_AB = cir(zero_state(N))\n", " rho_AB = cir(zero_state(N))\n",
" \n", " \n",
" # Calculate partial trace to obtain the quantum state rho_B of subsystem B\n", " # Calculate partial trace to obtain the quantum state rho_B of subsystem B\n",
" rho_B = partial_trace(rho_AB, 2 ** (N - N_SYS_B), 2 ** (N_SYS_B), 1)\n", " rho_B = partial_trace(rho_AB.data, 2 ** (N - N_SYS_B), 2 ** (N_SYS_B), 1)\n",
" \n", " \n",
" # Calculate the three parts of the loss function\n", " # Calculate the three parts of the loss function\n",
" rho_B_squre = rho_B @ rho_B\n", " rho_B_squre = rho_B @ rho_B\n",
...@@ -274,7 +276,7 @@ ...@@ -274,7 +276,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 13, "execution_count": 6,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:56:40.966455Z", "end_time": "2021-04-30T08:56:40.966455Z",
...@@ -307,7 +309,7 @@ ...@@ -307,7 +309,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 14, "execution_count": 8,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T08:56:50.509486Z", "end_time": "2021-04-30T08:56:50.509486Z",
...@@ -319,14 +321,14 @@ ...@@ -319,14 +321,14 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"iter: 10 loss: -3.1084 fid: 0.9241\n", "iter: 10 loss: -3.1085 fid: 0.9241\n",
"iter: 20 loss: -3.3375 fid: 0.9799\n", "iter: 20 loss: -3.3375 fid: 0.9799\n",
"iter: 30 loss: -3.3692 fid: 0.9897\n", "iter: 30 loss: -3.3692 fid: 0.9897\n",
"iter: 40 loss: -3.3990 fid: 0.9929\n", "iter: 40 loss: -3.3990 fid: 0.9929\n",
"iter: 50 loss: -3.4133 fid: 0.9959\n", "iter: 50 loss: -3.4133 fid: 0.9959\n",
"\n", "\n",
"The trained circuit: \n", "The trained circuit: \n",
"--Ry(6.290)----*--------------x----Ry(0.260)--\n", "--Ry(6.290)----*--------------x----Ry(0.747)--\n",
" | | \n", " | | \n",
"--Ry(4.745)----x----*---------|----Ry(6.249)--\n", "--Ry(4.745)----x----*---------|----Ry(6.249)--\n",
" | | \n", " | | \n",
...@@ -340,9 +342,6 @@ ...@@ -340,9 +342,6 @@
"source": [ "source": [
"paddle.seed(SEED)\n", "paddle.seed(SEED)\n",
" \n", " \n",
"# Set calculation mode as density matrix \n",
"paddle_quantum.set_backend(backend='density_matrix') \n",
" \n",
"# Convert Numpy array to Tensor supported in PaddlePaddle\n", "# Convert Numpy array to Tensor supported in PaddlePaddle\n",
"H = paddle.to_tensor(hamiltonian)\n", "H = paddle.to_tensor(hamiltonian)\n",
"\n", "\n",
...@@ -365,7 +364,7 @@ ...@@ -365,7 +364,7 @@
" opt.clear_grad()\n", " opt.clear_grad()\n",
"\n", "\n",
" # Convert to Numpy array to calculate the fidelity of the quantum state F(rho_B, rho_G)\n", " # Convert to Numpy array to calculate the fidelity of the quantum state F(rho_B, rho_G)\n",
" fid = state_fidelity(State(rho_B), State(rho_G))\n", " fid = state_fidelity(rho_B, rho_G)\n",
" \n", " \n",
" # Print training results\n", " # Print training results\n",
" if itr % 10 == 0:\n", " if itr % 10 == 0:\n",
...@@ -399,7 +398,7 @@ ...@@ -399,7 +398,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3.7.13 ('py3.7_pq2.2.1')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -413,7 +412,7 @@ ...@@ -413,7 +412,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.13" "version": "3.7.13"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
...@@ -427,6 +426,11 @@ ...@@ -427,6 +426,11 @@
"toc_position": {}, "toc_position": {},
"toc_section_display": true, "toc_section_display": true,
"toc_window_display": false "toc_window_display": false
},
"vscode": {
"interpreter": {
"hash": "4e4e2eb86ad73936e915e7c7629a18a8ca06348106cf3e66676b9578cb1a47dd"
}
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -554,7 +554,7 @@ ...@@ -554,7 +554,7 @@
" t = 1\n", " t = 1\n",
" cir = Circuit()\n", " cir = Circuit()\n",
" construct_trotter_circuit(cir, h_2, tau=t/n_steps, steps=n_steps)\n", " construct_trotter_circuit(cir, h_2, tau=t/n_steps, steps=n_steps)\n",
" return gate_fidelity(cir.unitary_matrix(), linalg.expm(-1 * 1j * h_2.construct_h_matrix())).item()\n", " return gate_fidelity(cir.unitary_matrix(), linalg.expm(-1 * 1j * h_2.construct_h_matrix()))\n",
"plt.axhline(1, ls='--', color='black')\n", "plt.axhline(1, ls='--', color='black')\n",
"plt.semilogy(np.arange(1, 11), [get_fid(r) for r in np.arange(1, 11)], 'o-')\n", "plt.semilogy(np.arange(1, 11), [get_fid(r) for r in np.arange(1, 11)], 'o-')\n",
"plt.xlabel('number of steps', fontsize=12)\n", "plt.xlabel('number of steps', fontsize=12)\n",
......
...@@ -554,7 +554,7 @@ ...@@ -554,7 +554,7 @@
" t = 1\n", " t = 1\n",
" cir = Circuit()\n", " cir = Circuit()\n",
" construct_trotter_circuit(cir, h_2, tau=t/n_steps, steps=n_steps)\n", " construct_trotter_circuit(cir, h_2, tau=t/n_steps, steps=n_steps)\n",
" return gate_fidelity(cir.unitary_matrix(), linalg.expm(-1 * 1j * h_2.construct_h_matrix())).item()\n", " return gate_fidelity(cir.unitary_matrix(), linalg.expm(-1 * 1j * h_2.construct_h_matrix()))\n",
"plt.axhline(1, ls='--', color='black')\n", "plt.axhline(1, ls='--', color='black')\n",
"plt.semilogy(np.arange(1, 11), [get_fid(r) for r in np.arange(1, 11)], 'o-')\n", "plt.semilogy(np.arange(1, 11), [get_fid(r) for r in np.arange(1, 11)], 'o-')\n",
"plt.xlabel('number of steps', fontsize=12)\n", "plt.xlabel('number of steps', fontsize=12)\n",
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 利用 qDRIFT 模拟时间演化\n",
"<em> Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 概述\n",
"量子力学中系统的能量由哈密顿量算符 $H$ 描述,它决定了系统演化的性质,模拟哈密顿量的时间演化,在建模复杂的化学和物理系统方面具有巨大的实用价值。然而,由于系统的自由度随系统(如量子比特数)增大呈指数级增加,导致一般情况下无法利用经典计算机有效模拟量子系统。当前使用量子计算机模拟哈密顿量的主要技术是利用乘积式方法(product formula)模拟时间演化,本教程将介绍关于 product formula 的一些基础理论和方法,和基于 product formula 改进的 quantum stochastic drift protocol (qDRIFT) —— 一种随机的 product formula 方法,并在文末进行了代码演示。\n",
"\n",
"## 利用 Product Formula 模拟时间演化\n",
"\n",
"根据量子力学的基本公理,在确定了一个系统的哈密顿量 $H$ 之后,该系统随时间演化的过程可以由如下方程描述\n",
"\n",
"$$\n",
"i \\hbar \\frac{d}{d t} | \\psi \\rangle = H | \\psi \\rangle,\n",
"\\tag{1}\n",
"$$\n",
"\n",
"其中 $\\hbar$ 为约化普朗克常数。因此,对于一个不含时的哈密顿量,系统的时间演化方程可以写为\n",
"\n",
"$$\n",
"|\\psi(t) \\rangle = U(t) | \\psi (0) \\rangle, ~ U(t) = e^{- i H t}.\n",
"\\tag{2}\n",
"$$\n",
"\n",
"这里我们取自然单位 $\\hbar=1$,$U(t)$ 为时间演化算符。利用量子电路来模拟时间演化过程的核心思想是利用量子电路构建出的酉变换模拟和近似该时间演化算符。Seth Lloyd 在其 1996 年的文章中指出,可以将一整段的演化时间 $t$ 拆分为 $r$ 份较短的“时间片段”来减小模拟时间演化的误差 [1]。考虑一个一般的哈密顿量形式 $H = \\sum_{k=1}^{L} H_k$,其中 $H_k$ 是作用在部分系统上的子哈密顿量。我们考虑每个子哈密顿量 $H_k$ 的演化算符为$e^{-i H_k t}$,我们依次模拟每个子哈密顿量可以得到 $\\prod_{k=1}^{L} e^{-i H_k t}$。通过泰勒展开,可以发现\n",
"\n",
"$$\n",
"e^{-iHt} = \\prod_{k=1}^{L} e^{-i H_k t} + O(t^2).\n",
"\\tag{3}\n",
"$$\n",
"\n",
"那么,我们令 $\\tau = t/r$,并考虑演化算符 $\\left(e^{-iH \\tau}\\right)^r$,就可以推导出\n",
"\n",
"$$\n",
"e^{-iHt} = \\left(e^{-iH \\tau}\\right)^r = \\left(\\prod_{k=1}^{L} e^{-i H_k \\tau} + O(\\tau^2) \\right)^r = \\left(\\prod_{k=1}^{L} e^{-i H_k \\tau} \\right)^r + O\\left(\\frac{t^2}{r}\\right).\n",
"\\tag{4}\n",
"$$\n",
"\n",
"上式告诉我们,只要将一整段演化时间拆为足够多的“片段”,就可以达到任意高的模拟精度,这就是 product formula 的基本思想。不过,(4) 中给出的只是一个粗略的估计。如果我们想要估计达到某一模拟精度所需要的量子电路深度,就需要推导其更严格的误差上界。具体地,我们令 $U_{circuit}$ 代表我们构造的电路,$\\Vert \\cdot \\Vert$ 为 Schatten-$\\infty$ 范数,即[谱范数](https://en.wikipedia.org/wiki/Schatten_norm)。那么其模拟误差 $\\epsilon$ 可以写为\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"\\epsilon\\left(e^{-iHt}, U_{circuit}\\right) & = \\Vert e^{-iHt} - U_{circuit}\\Vert .\n",
"\\end{aligned}\n",
"\\tag{5}\n",
"$$\n",
"下面,我们展示一个比较简略的误差上界计算过程,我们不加证明地列出 (6)、(7) 两个结论,会在证明 (8) 时用到,感兴趣的读者可以参考 [2] 中的 F.1 节获取证明细节。\n",
"$$\n",
"\\left\\Vert \\mathcal{R}_k \\left( \\prod_{k=1}^{L} e^{-i H_k \\tau} \\right) \\right\\Vert\n",
"\\leq\n",
"\\mathcal{R}_k \\left( e^{\\vert \\tau \\vert \\sum_{k=1}^{L} \\Vert H_k \\Vert } \\right),\n",
"\\tag{6}\n",
"$$\n",
"\n",
"$$\n",
"\\vert \\mathcal{R}_k(e^\\alpha) \\vert \\leq \\frac{\\vert \\alpha \\vert^{k+1}}{(k+1)!} e^{ \\vert \\alpha \\vert }, ~\n",
"\\forall \\alpha \\in \\mathbb{C},\n",
"\\tag{7}\n",
"$$\n",
"\n",
"其中 $\\mathcal{R}_k(f)$为函数 $f$ 泰勒展开至 $k$ 阶之后的余项,例如 $\\mathcal{R}_1 (e^x)=\\mathcal{R}_1 (\\sum_{j=0}^\\infty \\frac{x^n}{n!})=\\sum_{j=2}^\\infty \\frac{x^n}{n!}$。\n",
"令 $\\Lambda = \\max_k \\Vert H_k \\Vert$,考虑完整的演化时间 $t = r \\cdot \\tau$,那么模拟长度为 $t$ 的时间演化算符时的误差为:\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"\\left \\Vert \\left ( e^{-i\\tau \\sum_{k=1}^L H_k }\\right)^r - \\left (\\prod_{k=1}^{L} e^{-i H_k \\tau} \\right)^r \\right \\Vert \\leq &\n",
"r \\left \\Vert e^{-i\\tau \\sum_{k=1}^L H_k } - \\prod_{k=1}^{L} e^{-i H_k \\tau } \\right \\Vert \\\\\n",
"=& r \\left \\Vert \\mathcal{R}_1 \\left( e^{-i\\tau \\sum_{k=1}^L H_k} \\right)- \\mathcal{R}_1 \\left( \\prod_{k=1}^{L} e^{-i H_k \\tau } \\right) \\right \\Vert \\\\\n",
"\\leq& r \\left \\Vert \\mathcal{R}_1 \\left( e^{-i\\tau \\sum_{k=1}^L H_k} \\right) \\right \\Vert+ r\\left \\Vert \\mathcal{R}_1 \\left( \\prod_{k=1}^{L} e^{-i H_k \\tau } \\right) \\right \\Vert \\\\\n",
"\\leq& 2r \\left \\Vert \\mathcal{R}_1 \\left( e^{-i |\\tau | \\sum_{k=1}^L \\Vert H_k \\Vert} \\right) \\right \\Vert \\\\\n",
"\\leq& 2r \\left \\Vert \\mathcal{R}_1 \\left( e^{-i |\\tau | L \\Lambda} \\right) \\right \\Vert \\\\\n",
"\\leq& r ( \\tau L \\Lambda )^2 e^{\\vert \\tau \\vert L \\Lambda } \\\\\n",
"=&\\frac{( t L \\Lambda )^2}{r} e^{\\frac{\\vert t \\vert L \\Lambda}{r} }.\n",
"\\end{aligned}\n",
"\\tag{8}\n",
"$$\n",
"\n",
"其中这里用到了量子电路中误差线性累积的结论,即 $\\Vert U^r - V^r \\Vert \\leq r\\Vert U - V \\Vert$,不熟悉这一结论的读者可以参考 [3] 中的 4.5.3 节;也用到了 (7) 式中 $k=1$ 时的结论。至此,我们就计算出了 product formula 对于一段完整的演化时间 $t$ 的模拟误差上界,即 (4) 式中的二阶项 $O(t^2/r)$。 \n",
"\n",
"\n",
"在得到了模拟误差上界的基础上,便可以进一步计算达到一定精度 $\\epsilon$ 时所需要的电路深度的下界。从 (8) 中我们不难发现,式子里含有 $L$ 项,这就意味着,随着哈密顿量项数的增加,若需控制误差上界,则时间片段的划分必须越来越细,这就使得电路深度增加。本文所要介绍的 qDRIFT 在一定程度上解决了该问题。qDRIFT 着眼于哈密顿量本身的系数,将其建模为一个概率分布,每次从该概率分布中采样酉门并重复一定的次数,从而构成量子电路,最终在给定的精度下,其量子电路的深度将不显含哈密顿量项数 $L$。下面我们将详细介绍它。\n",
"\n",
"\n",
"## 利用 qDRIFT 模拟时间演化\n",
"首先,我们给出目标哈密顿量的形式\n",
"$$\n",
"H=\\sum_{j=1}^L h_j H_j,\n",
"\\tag{9}\n",
"$$\n",
"它含有 $L$ 项子哈密顿量 $H_j$,值得注意的是,这里的 $H_j$ 是已经被归一化了的,也就是 $\\Vert H_j \\Vert = 1$,其中 $\\Vert\\cdot\\Vert$ 为 Schatten-$\\infty$ 范数 ,$h_j$ 是每个子哈密顿量的系数,它是一个正实数。通过该系数我们便可以构造一个离散的概率分布,以单个系数在整个哈密顿量系数总和的占比作为每个酉门被采样的概率,也就是 $p_j =h_j / \\lambda$,其中 $\\lambda =\\sum _j h_j$ 是系数和,如此采样重复 $N$ 次(为了与 product formula 对照,我们取 $ N=Lr$),我们就得到一个由 $j$ 排列的有序列表,并可以根据该排列构造酉门 $U_j = e^{i\\tau H_j}$ 。假设 $L=3$ ,$r=2$,我们可以根据上述概率分布采样一个有序列表形如\n",
"$$\n",
"[ 3, 1, 2 ,3 ,3 ,1 ],\n",
"$$\n",
"那么就可以据此构造量子电路\n",
"$$\n",
"U_{circuit} = e^{i\\tau H_1}e^{i\\tau H_3}e^{i\\tau H_3}e^{i\\tau H_2}e^{i\\tau H_1}e^{i\\tau H_3},\n",
"$$\n",
"$\\tau=t\\lambda /N$,这就是 qDRIFT 模拟哈密顿量的一个实现。\n",
"\n",
"qDRIFT 的实现流程非常简单,而它的优势在于,在给定目标精度 $\\epsilon$ 时其酉门数的复杂度为 $O((\\lambda t)^2 /\\epsilon) $,可以看到这是一个不含 $L$ 的结果,也就是说,其酉门数量与哈密顿量的项数不显式相关,这在哈密顿量项数很大时可以有效地缩减模拟电路的长度。接下来我们将给出证明。\n",
"\n",
"我们将根据概率分布进行采样的过程建模为一个量子信道,我们用花体字母 $\\mathcal{E}$ 和 $\\mathcal{U}$ 分别来表示通过 qDRIFT 建立的信道和所要模拟的信道,并且用 $\\mathcal{E}_N$ 和 $\\mathcal{U}_N$ 代表其各自信道对量子态 $\\rho$ 的 $N$ 次作用中的一次作用,即\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"&\\mathcal{U}_N (\\rho) = e^{\\frac{it}{N}H} \\rho e^{\\frac{-it}{N}H}= e^{\\frac{t}{N}\\mathcal{L}} (\\rho),\n",
"\\\\\n",
"&\\mathcal{E}_N (\\rho)=\\sum_{j}p_j e^{i\\tau H_j} \\rho e^{-i\\tau H_j}=\\sum_{j} p_j e^{\\tau \\mathcal{L}_j}(\\rho).\n",
"\\end{aligned}\n",
"\\tag{10}\n",
"$$\n",
"\n",
"这里我们引入 Liouvillian 表示 ,即对量子信道 $\\mathcal{P}(\\rho)=e^{iHt}\\rho e^{-iHt}$ 有\n",
"$$\n",
"\\mathcal{P}(\\rho)=e^{iHt}\\rho e^{-iHt}=e^{t\\mathcal{L}}(\\rho)=\\sum_{k=0}^\\infty \\frac{t^k \\mathcal{L}^k (\\rho)}{k!},\n",
"\\tag{11}\n",
"$$\n",
"\n",
"\n",
"其中 $\\mathcal{L}(\\rho)=i(H\\rho - \\rho H)$ ,同理有 $\\mathcal{L}_j(\\rho)=i(H_j\\rho - \\rho H_j)$ 。需要注意的是,$\\mathcal{L}$ 的迭代规则遵循 $\\mathcal{L}^{n+1}(\\rho)=i(H\\mathcal{L}^n(\\rho)-\\mathcal{L}^n(\\rho)H)$。具体来说,$\\mathcal{U}_N = \\sum_{n=0}^\\infty \\frac{t^n\\mathcal{L}^n}{n!N^n}$,$\\mathcal{E}_N =\\sum_{j}p_j \\sum_{n=0}^\\infty \\frac{\\lambda^n t^n \\mathcal{L}_j^n}{n!N^n}$。接下来我们该如何度量两个信道的距离呢?这里引入[菱形范数](https://en.wikipedia.org/wiki/Diamond_norm) (diamond norm) 的定义式\n",
"$$\n",
"\\begin{aligned}\n",
"\\Vert \\mathcal{P} \\Vert_\\Diamond :=\\sup_{\\rho ; \\Vert \\rho \\Vert _1 =1}\\Vert (\\mathcal{P} \\otimes \\mathbb{I})(\\rho )\\Vert _1 .\n",
"\\end{aligned}\n",
"\\tag{12}\n",
"$$\n",
"其中 $\\mathbb{I}$ 为与 $\\mathcal{P}$ 空间相同大小的单位信道,$\\Vert \\cdot \\Vert_1$ 为 Schatten-$1$ 范数,即[迹范数](https://en.wikipedia.org/wiki/Schatten_norm)。我们使用菱形范数定义两个量子信道的距离\n",
"$$\n",
"\\begin{aligned}\n",
"d_\\Diamond (\\mathcal{E},\\mathcal{U}) &=\\frac{1}{2} \\Vert \\mathcal{E} -\\mathcal{U} \\Vert_\\Diamond\n",
"\\\\\n",
"&=\\sup_{\\rho ; \\Vert \\rho \\Vert _1 =1} \\frac{1}{2} \\Vert ((\\mathcal{E}-\\mathcal{U}) \\otimes \\mathbb{I})(\\rho )\\Vert _1 .\n",
"\\end{aligned}\n",
"\\tag{13}\n",
"$$\n",
"菱形范数代表了在所有量子态中能够分辨两个信道的最大可能性,它的值越大,两个信道被区分的可能性就越大,也就代表了两个信道距离远,模拟效果差;反之它的值小,就代表模拟效果好。接着,我们可以去计算单次作用的信道的距离上界\n",
"$$\n",
"\\begin{aligned}\n",
" \\Vert \\mathcal{U}_N-\\mathcal{E}_N \\Vert_\\Diamond &= \\left\\Vert \\sum_{n=2}^\\infty \\frac{t^n\\mathcal{L}^n}{n!N^n}-\\sum_{j}\\frac{h_j}{\\lambda} \\sum_{n=2}^\\infty \\frac{\\lambda^n t^n \\mathcal{L}_j^n}{n!N^n} \\right\\Vert_\\Diamond\\\\\n",
" &\\leq \\sum_{n=2}^\\infty \\frac{t^n\\Vert \\mathcal{L}^n \\Vert_\\Diamond }{n!N^n} + \\sum_{j}\\frac{h_j}{\\lambda} \\sum_{n=2}^\\infty \\frac{\\lambda^n t^n \\Vert\\mathcal{L}_j^n \\Vert_\\Diamond }{n!N^n}\\\\\n",
" &\\leq \\sum_{n=2}^\\infty \\frac{1}{n!}\\left( \\frac{2\\lambda t}{N}\\right)^n+\\sum_{j}\\frac{h_j}{\\lambda} \\sum_{n=2}^\\infty \\frac{1}{n!}\\left( \\frac{2\\lambda t}{N}\\right)^n\\\\\n",
" &=2\\sum_{n=2}^\\infty \\frac{1}{n!}\\left( \\frac{2\\lambda t}{N}\\right)^n .\n",
"\\end{aligned}\n",
"\\tag{14}\n",
"$$\n",
"其中这里用到了结论 $\\Vert \\mathcal{L} \\Vert_\\Diamond \\leq 2\\Vert H\\Vert \\leq 2\\lambda$ ,同理有 $\\Vert \\mathcal{L}_j \\Vert_\\Diamond \\leq 2\\Vert H_j\\Vert \\leq 2$ [4]。接着,我们可以利用上文中提到的 (7) 的结论,令 $k=1$,$\\alpha=2\\lambda t /N$ 便可得到\n",
"\n",
"$$\n",
"d_\\Diamond (\\mathcal{U}_N,\\mathcal{E}_N) \\leq \\frac{2\\lambda^2 t^2}{N^2} e^{2\\lambda t/N} ,\n",
"\\tag{15}\n",
"$$\n",
"然后再次利用 $\\Vert U^r - V^r \\Vert \\leq r\\Vert U - V \\Vert$ 这一结论(需要注意,式子中的 $U$ 与 $V$ 本是线性算子,但对于量子信道 $\\mathcal{U}$ 和 $\\mathcal{E}$ 依然适用,感兴趣的读者可以参考 [6] 中的第 3.3.2 节获取证明细节),且通常情况下 $2\\lambda t \\ll N$,便可推出\n",
"$$\n",
"\\begin{aligned}\n",
"d_\\Diamond (\\mathcal{U},\\mathcal{E}) &\\leq N d_\\Diamond (\\mathcal{U}_N, \\mathcal{E}_N)\\\\\n",
" &=\\frac{2\\lambda^2 t^2}{N} e^{2\\lambda t/N} \\approx \\frac{2\\lambda^2 t^2}{N}.\n",
"\\end{aligned}\n",
"\\tag{16}\n",
"$$\n",
"因此 $ N \\sim O((\\lambda t)^2 /\\epsilon)$。 由上式可以看出,在满足 $\\lambda \\ll \\Lambda L$ 的条件下(回忆一下,$\\Lambda = \\max_k \\Vert H_k \\Vert$,qDRIFT 将哈密顿量写为 $H=\\sum_{j=1}^L h_j H_j$,那么对应的 $\\Lambda = \\max_k h_k $),其距离将不与 $L$ 显式相关,这也就可以在 $L$ 较大即情况较为复杂时,不会带来量子电路深度的显著增加,可以有效控制酉门的数量。很多物理系统的哈密顿量都满足 $\\lambda \\ll \\Lambda L$,如乙烷、二氧化碳的电子结构,但并非所有情况都满足,若 $\\lambda = \\Lambda L$ 或 $\\lambda = \\Lambda \\sqrt{L}$ 时,它们的上界分别为 $O(L^2(\\Lambda t)^2 /\\epsilon)$ 和 $O(L(\\Lambda t)^2 /\\epsilon)$ ,可以看到,它们仍然随着哈密顿量项数增大而增大。感兴趣的读者可以参考 [4] 获取更多细节。\n",
"\n",
"\n",
"## 代码实现\n",
"我们将结合实际代码实现 qDRIFT。我们将首先演示其采样结果的性能,再计算其信道的模拟误差。首先我们需要导入需要的包。"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"import warnings\n",
"\n",
"import math\n",
"import numpy as np \n",
"import scipy \n",
"import paddle_quantum as pq \n",
"import paddle\n",
"\n",
"\n",
"warnings.filterwarnings(\"ignore\") # 隐藏 warnings\n",
"np.set_printoptions(suppress=True, linewidth=np.nan) # 启用完整显示,便于在终端 print 观察矩阵时不引入换行符\n",
"pq.set_backend('density_matrix') # 使用密度矩阵表示"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们假设系统由 2 个 qubits 组成,我们可以利用量桨的 `hamiltonian` 模块构造一个哈密顿量项数为 $L=4$ 的哈密顿量,为了演示 qDRIFT 的效果,我们选择一组满足 $\\lambda \\ll \\Lambda L$ 的参数,这便是我们的目标哈密顿量,具体如下\n",
"$$\n",
"\\begin{aligned}\n",
"H&=I \\otimes X + 0.05 * X \\otimes Z + 0.05 * I \\otimes Y + 0.05 * X \\otimes X .\n",
"\\end{aligned}\n",
"$$"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"目标哈密顿量为: \n",
" [[ 0. +0.j 1. -0.05j 0.05+0.j 0.05+0.j ]\n",
" [ 1. +0.05j 0. +0.j 0.05+0.j -0.05+0.j ]\n",
" [ 0.05+0.j 0.05+0.j 0. +0.j 1. -0.05j]\n",
" [ 0.05+0.j -0.05+0.j 1. +0.05j 0. +0.j ]]\n"
]
}
],
"source": [
"qubits = 2 # 设置量子比特数\n",
"H_j = [(1.0, 'I0,X1'), # 构造哈密顿量的泡利串\n",
" (0.05, 'X0,Z1'),\n",
" (0.05, 'I0,Y1'),\n",
" (0.05, 'X0,X1'), ]\n",
"\n",
"H = pq.hamiltonian.Hamiltonian(H_j) \n",
"print(f'目标哈密顿量为: \\n {H.construct_h_matrix(qubit_num=qubits)}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"接下来,我们根据 $\\lambda = \\sum_j h_j$,$ p_j=h_j/\\lambda $ 计算概率。在本次实验中,假设我们的目标精度 $\\epsilon=0.1$,模拟时间 $t=1$,也就是说,我们需要采样 $N=\\lceil \\frac{2\\lambda^2 t^2}{\\epsilon}\\rceil = 27$ 次。"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"达到 0.1 的精度需要 27 个酉门\n"
]
}
],
"source": [
"h_j = np.array(H.coefficients) # 获取系数\n",
"lamda = h_j.sum()\n",
"p_j = h_j/lamda # 计算离散概率分布\n",
"accuracy = 0.1\n",
"t = 1\n",
"gate_counts = math.ceil(2 * lamda**2 * t**2 / accuracy)\n",
"\n",
"print(f'达到 {accuracy} 的精度需要 {gate_counts} 个酉门')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"接着,我们将根据概率分布 $p_j$ 独立采样 27 次,并根据该采样结果构造酉电路。"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"采样结果为:\n",
" [1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 4 1]\n",
"qDRIFT 的模拟电路矩阵为: \n",
" [[ 0.51998969-0.02531459j 0.03408183+0.85099285j -0.03524884+0.02376227j -0.0353899 +0.02366261j]\n",
" [-0.03408183+0.85099285j 0.51998969+0.02531459j 0.0353899 +0.02366261j -0.03524884-0.02376227j]\n",
" [-0.03524884+0.02376227j -0.0353899 +0.02366261j 0.51998969-0.02531459j 0.03408183+0.85099285j]\n",
" [ 0.0353899 +0.02366261j -0.03524884-0.02376227j -0.03408183+0.85099285j 0.51998969+0.02531459j]] \n",
"原始电路矩阵为: \n",
" [[ 0.53752508-0.00075235j 0.04202098+0.83966719j -0.04201839+0.04202098j -0.00075235+0.02697398j]\n",
" [-0.04202098+0.83966719j 0.53752508+0.00075235j 0.00075235+0.02697398j -0.04201839-0.04202098j]\n",
" [-0.04201839+0.04202098j -0.00075235+0.02697398j 0.53752508-0.00075235j 0.04202098+0.83966719j]\n",
" [ 0.00075235+0.02697398j -0.04201839-0.04202098j -0.04202098+0.83966719j 0.53752508+0.00075235j]]\n"
]
}
],
"source": [
"np.random.seed(666) # 固定随机数初始位置,便于演示说明\n",
"sample_list = np.random.choice(a=range(1, 5), size=gate_counts, replace=True, p=p_j)\n",
"print(f'采样结果为:\\n {sample_list}')\n",
"\n",
"# 根据采样结果计算采样出来的酉电路\n",
"simulation = np.identity(2 ** qubits) # 生成单位矩阵\n",
"tau = 1j*lamda*t/gate_counts\n",
"for i in sample_list:\n",
" pauli_str_j = (1.0, H_j[i-1][1]) # 获取H_j,注意,应抛弃其原有系数\n",
" H_i = pq.hamiltonian.Hamiltonian([pauli_str_j]).construct_h_matrix(qubit_num=qubits)\n",
" simulation = np.matmul(scipy.linalg.expm(tau*H_i), simulation) \n",
"origin = scipy.linalg.expm(1j*t*H.construct_h_matrix(qubit_num=qubits)) # 计算目标哈密顿量的原始电路\n",
"print(f'qDRIFT 的模拟电路矩阵为: \\n {simulation} \\n原始电路矩阵为: \\n {origin}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"然后我们便可计算出从 qDRIFT 采样出来的酉电路和原始电路之间的模拟误差 $\\Vert e^{iHt}-U_{circuit}\\Vert$,注意区分,这里的范数为谱范数。 "
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"模拟误差为: 0.0309\n"
]
}
],
"source": [
"distance = 0.5 * np.linalg.norm(origin-simulation, ord=2)\n",
"print(f'模拟误差为: {distance:.4f}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"当然我们可以带入一个具体的量子态试验一下,不失一般性,我们假设初始量子态为零态,即 $\\rho(0) = | 0 \\rangle \\langle 0 | $,本教程的实验我们均使用密度矩阵描述量子态。我们可以让量子态分别通过原始方法和 qDRIFT 模拟方法演化,到 $t$ 时刻量子态分别为 $\\rho(t)_{origin}$ 和 $\\rho(t)_{qDRIFT}$,最后可以比较这两个量子态的保真度来衡量模拟电路的效果。"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"初始量子态为 \n",
" [[1.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
" [0.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
" [0.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
" [0.+0.j 0.+0.j 0.+0.j 0.+0.j]]\n",
"两个量子态之间的保真度为0.9989\n"
]
}
],
"source": [
"rho_0 = pq.state.zero_state(qubits).numpy() # 构造零态密度矩阵\n",
"print(f'初始量子态为 \\n {rho_0}')\n",
"\n",
"rho_t_origin = pq.state.to_state(origin @ rho_0 @ origin.T.conjugate()) # 经过原始电路演化\n",
"rho_t_qdrift = pq.state.to_state(simulation @ rho_0 @ simulation.T.conjugate()) # 经过模拟电路演化\n",
"fidelity = pq.qinfo.state_fidelity(rho_t_origin, rho_t_qdrift)\n",
"print(f'两个量子态之间的保真度为{float(fidelity):.4f}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"可以发现,上面的测试均符合我们的精度要求。但区别于根据 qDRIFT 方法采样得到的某个具体的酉电路,我们将 qDRIFT 的采样方法看作是一个量子信道,也即对量子态 $\\rho$ 的一个映射。上面的实验只是这个信道的一次具体表达,我们接下来将分析这个信道的性能。我们可以定义一个函数,用于描绘 qDRIFT 信道。"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"# 定义 qDRIFT 信道\n",
"def qdrift_channel(iter_num, sample_num, hamiltonian_list, coefficient_list, simulation_time, qubits, input_state):\n",
" '''\n",
" 输入 :\n",
" iter_num : 当前迭代次数,作为递归的标记\n",
" sample_num : 采样次数,即 N\n",
" hamiltonian_list : 目标哈密顿量的泡利串形式的列表,即 H_j\n",
" coefficient_list : 子哈密顿量的系数列表,即 h_j\n",
" simulation_time : 模拟时间,即 t\n",
" qubits : 系统的量子比特数\n",
" input_state : 输入的量子态,应为密度算子\n",
" \n",
" 输出 :\n",
" 经过该 qDRIFT 信道的量子态(密度算子表示)\n",
" '''\n",
" lamda = coefficient_list.sum() \n",
" tau = lamda*simulation_time/sample_num\n",
" output = 0\n",
"\n",
" if iter_num != 1: # 在迭代标志不为 1 的时候启用递归\n",
" input_state = qdrift_channel(iter_num-1, sample_num, hamiltonian_list,\n",
" coefficient_list, simulation_time, qubits, input_state)\n",
"\n",
" # 计算 e^{iH\\tau} \\rho e^{-iH\\tau} \n",
" for sub_H, sub_h in zip(hamiltonian_list, coefficient_list):\n",
" sub_H = pq.hamiltonian.Hamiltonian([sub_H]).construct_h_matrix(qubit_num=qubits)\n",
" unitary = scipy.linalg.expm(1j*tau*sub_H) # 计算 e^{iH\\tau}\n",
" output += sub_h/lamda*unitary @ input_state @ unitary.conjugate().T\n",
" return output"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"接着我们便可以通过菱形范数计算两个信道的距离,不过菱形范数的求解可以转换为半正定规划问题,即\n",
"\n",
"$$\n",
"d_\\Diamond(\\mathcal{U}- \\mathcal{E})=\\sup_{\\Omega \\geq 0 \\atop \\rho \\geq 0}\\{\\text{Tr}[\\Omega (\\Gamma_\\mathcal{U}-\\Gamma_\\mathcal{E})]: \\Omega \\leq \\rho \\otimes \\mathbb{I},\\text{Tr} (\\rho)=1\\},\n",
"\\tag{17}\n",
"$$\n",
"其中 $\\Gamma_\\mathcal{U}$ 与 $\\Gamma_\\mathcal{E}$ 为原始信道和模拟信道的 Choi 表示。菱形范数的半正定规划和 Choi 表示有多种形式,感兴趣的读者可以阅读 [6-8] 获取更多细节。我们这里使用的 Choi 表示具体为\n",
"$$\n",
"\\Gamma_\\mathcal{P}=\\sum_{i,j=0}^{d-1} |i\\rangle \\langle j| \\otimes \\mathcal{P}(|i\\rangle \\langle j|),\n",
"\\tag{18}\n",
"$$\n",
"其中 $\\mathcal{P}$ 为量子信道,$d$ 为该量子信道输入量子态的维度。这里我们首先计算两个信道的 Choi 表示。\n"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"# 计算原始信道和 qDRIFT 信道的 Choi 表示,在该表示下可以进而计算菱形范数\n",
"choi_qdrift = 0\n",
"choi_origin = 0\n",
"channel = scipy.linalg.expm(1j*t*H.construct_h_matrix(qubit_num=qubits))\n",
"for i in range(2 ** qubits):\n",
" for k in range(2 ** qubits):\n",
" choi_temp = np.zeros((2 ** qubits, 2 ** qubits))\n",
" choi_temp[i][k] = 1 # 生成 |i\\rangle \\langle k|\n",
"\n",
" # 分两步计算信道 E 的 Choi 表示\n",
" # 先计算 \\mathcal{E}(|i\\rangle \\langle k|)\n",
" choi_temp_qdrift = qdrift_channel(gate_counts, gate_counts, H_j, h_j, t, qubits, choi_temp) \n",
" # 再计算 |i\\rangle \\langle k| \\otimes \\mathcal{E}(|i\\rangle \\langle k|)\n",
" choi_qdrift += np.kron(choi_temp, choi_temp_qdrift)\n",
"\n",
" # 分两步计算信道 U 的 Choi 表示\n",
" # 先计算 \\mathcal{U}(|i\\rangle \\langle k|)\n",
" choi_temp_origin = channel @ choi_temp @ channel.T.conjugate()\n",
" # 再计算 |i\\rangle \\langle k| \\otimes \\mathcal{U}(|i\\rangle \\langle k|)\n",
" choi_origin += np.kron(choi_temp, choi_temp_origin)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"接着我们可以按照 (17) 式计算菱形范数,并求取两个信道的菱形距离。"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"两个信道之间的距离为: 0.0764\n"
]
}
],
"source": [
"print(f'两个信道之间的距离为: {0.5*pq.qinfo.diamond_norm(paddle.to_tensor(choi_origin-choi_qdrift)):.4f}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"可以看到,计算结果是符合预期的。值得注意的是,该值代表了该信道采样为具体模拟电路的最差表现的期望值,它并不能保证每个采样出来的电路都能够达到该精度。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 小结\n",
"\n",
"量子模拟本身是一个比较宽泛的话题,其应用也十分广泛。本教程介绍了 product formula 的理论基础和 qDRIFT 方法,并给出了 qDRIFT 的实现例子。但 qDRIFT 并非随机的 product formula 的唯一方法。作为使用 product formula 进行量子模拟的方法的一个分支,随机的 product formula 还有诸多方法值得我们去探究。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## 参考资料\n",
" \n",
"[1] Lloyd, Seth. \"Universal quantum simulators.\" [Science (1996): 1073-1078](https://www.jstor.org/stable/2899535).\n",
"\n",
"[2] Childs, Andrew M., et al. \"Toward the first quantum simulation with quantum speedup.\" [Proceedings of the National Academy of Sciences 115.38 (2018): 9456-9461](https://www.pnas.org/content/115/38/9456.short).\n",
"\n",
"[3] Nielsen, Michael A., and Isaac Chuang. \"Quantum computation and quantum information.\" (2002): 558-559.\n",
"\n",
"[4] Campbell, E. . \"Random Compiler for Fast Hamiltonian Simulation.\" [Physical Review Letters 123.7(2019):070503.1-070503.5](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.070503).\n",
"\n",
"[5] Khatri, Sumeet, and Mark M. Wilde. \"Principles of quantum communication theory: A modern approach.\" [arXiv preprint arXiv:2011.04672 (2020).](https://arxiv.org/abs/2011.04672)\n",
"\n",
"[6] Watrous, J. . [The Theory of Quantum Information](https://cs.uwaterloo.ca/~watrous/TQI/). 2018.\n",
"\n",
"[7] Watrous, J. . \"Simpler semidefinite programs for completely bounded norms.\" [Chicago Journal of Theoretical Computer Science (2012).](https://arxiv.org/abs/1207.5726)\n",
"\n",
"[8] Watrous, J. . \"Semidefinite Programs for Completely Bounded Norms.\" [Theory of Computing 5.1(2009):217-238.](https://arxiv.org/abs/0901.4709)\n",
"\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.7.13 ('py3.7_pq2.2.1')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.13"
},
"vscode": {
"interpreter": {
"hash": "4e4e2eb86ad73936e915e7c7629a18a8ca06348106cf3e66676b9578cb1a47dd"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Hamiltonian Simulation with qDRIFT\n",
"<em> Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Overview\n",
"In quantum mechanics, the energy of the system is described by the Hamiltonian operator $H$, which determines the evolution of the system. So Hamiltonian simulation has great practical value in modeling complex chemical and physical systems. However, because the degree of freedom of the system increases exponentially with the increase of the system (such as the number of qubits), it is generally impossible to use classical computers to effectively simulate quantum systems. At present, the main technology of using quantum computers to simulate Hamiltonian is to use product formula method to simulate time evolution. This tutorial will introduce some basic theories and methods about product formula, and a random method named quantum stochastic drift protocol (qDRIFT) which is based on product formula. Then we give a code demonstration at the end of the article.\n",
"\n",
"\n",
"## Product formula\n",
"\n",
"According to the basic axioms of quantum mechanics, the evolution of the system with Hamiltonian $H$ can be described by the following equation\n",
"\n",
"$$\n",
"i \\hbar \\frac{d}{d t} | \\psi \\rangle = H | \\psi \\rangle,\n",
"\\tag{1}\n",
"$$\n",
"\n",
"where $\\hbar$ is the reduced Planck constant. Therefore, for a time-independent Hamiltonian, the time evolution equation of the system can be written as\n",
"\n",
"$$\n",
"|\\psi(t) \\rangle = U(t) | \\psi (0) \\rangle, ~ U(t) = e^{- i H t}.\n",
"\\tag{2}\n",
"$$\n",
"\n",
"Here we take the natural unit $\\hbar=1$, $U (t) $ as the time evolution operator. The core idea of using quantum circuits to simulate the time evolution process is to use the unitary transformation constructed by quantum circuits to simulate and approximate the time evolution operator. Seth Lloyd pointed out in his 1996 article that a whole evolution time of $t$ can be divided into $r$ shorter \"time blocks\" to reduce the error of simulation in time evolution[1]. Consider a general Hamiltonian form $H = \\sum_ {k=1}^{L} H_k$, of which $H_k$ is sub-Hamiltonian acting on a part of the system. We consider each sub-Hamiltonian $H_k$ whose evolution operator is $e^ {-i H_k t}$, and we can get $\\prod_{k=1}^{L} e^{-i H_k t}$ by simulating each sub-Hamiltonian in turn. Through Taylor expansion, it can be found that\n",
"\n",
"$$\n",
"e^{-iHt} = \\prod_{k=1}^{L} e^{-i H_k t} + O(t^2).\n",
"\\tag{3}\n",
"$$\n",
"\n",
"Then let $\\tau = t/r$ and consider the evolution operator $\\left (e^ {-iH \\tau}\\right) ^r$, we can deduce that\n",
"\n",
"$$\n",
"e^{-iHt} = \\left(e^{-iH \\tau}\\right)^r = \\left(\\prod_{k=1}^{L} e^{-i H_k \\tau} + O(\\tau^2) \\right)^r = \\left(\\prod_{k=1}^{L} e^{-i H_k \\tau} \\right)^r + O\\left(\\frac{t^2}{r}\\right).\n",
"\\tag{4}\n",
"$$\n",
"\n",
"This formula tells us that as long as the whole evolution time can be divided into enough \"fragments\", we can simulate with any high simulation accuracy. That is the basic idea of the product formula. However, what is given in (4) is only a rough estimate. If we want to estimate the depth of the quantum circuits required to achieve a certain simulation accuracy, we need to calculate its rigorous error upper bound. Specifically, we make $U_ {circuit}$ represent the circuit we construct, $\\Vert \\cdot \\Vert$ is the Schatten-$\\infty$ norm, that is, the [spectral norm](https://en.wikipedia.org/wiki/Schatten_norm). Then the simulation error $\\epsilon$ can be written as\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"\\epsilon\\left(e^{-iH\\tau}, U_{circuit}\\right) & = \\Vert e^{-iH\\tau} - U_{circuit}\\Vert .\n",
"\\end{aligned}\n",
"\\tag{5}\n",
"$$\n",
"\n",
"Next, we will show a brief calculation process of the upper bound of error. We give two conclusions (6) and (7) without proof, which will be used in proving (8). Interested readers can refer to section F.1 in [2] for details.\n",
"\n",
"$$\n",
"\\left\\Vert \\mathcal{R}_k \\left( \\prod_{k=1}^{L} e^{-i H_k \\tau} \\right) \\right\\Vert\n",
"\\leq\n",
"\\mathcal{R}_k \\left( e^{\\vert \\tau \\vert \\sum_{k=1}^{L} \\Vert H_k \\Vert } \\right),\n",
"\\tag{6}\n",
"$$\n",
"\n",
"$$\n",
"\\vert \\mathcal{R}_k(e^\\alpha) \\vert \\leq \\frac{\\vert \\alpha \\vert^{k+1}}{(k+1)!} e^{ \\vert \\alpha \\vert }, ~\n",
"\\forall \\alpha \\in \\mathbb{C},\n",
"\\tag{7}\n",
"$$\n",
"\n",
"where $\\mathcal{R}_ k (f) $ is the remainder Taylor expansion to order $k$ of the function $f$, such as $\\mathcal{R}_1 (e^x)=\\mathcal{R}_1 (\\sum_{j=0}^\\infty \\frac{x^n}{n!})=\\sum_{j=2}^\\infty \\frac{x^n}{n!}$. \n",
"Make $\\Lambda = \\max_ k \\Vert H_ k \\Vert$, considering the complete evolution time $t = r \\cdot \\tau$, the error of simulation in complete time $t$ is:\n",
"$$\n",
"\\begin{aligned}\n",
"\\left \\Vert \\left ( e^{-i\\tau \\sum_{k=1}^L H_k }\\right)^r - \\left (\\prod_{k=1}^{L} e^{-i H_k \\tau} \\right)^r \\right \\Vert \\leq &\n",
"r \\left \\Vert e^{-i\\tau \\sum_{k=1}^L H_k } - \\prod_{k=1}^{L} e^{-i H_k \\tau } \\right \\Vert \\\\\n",
"=& r \\left \\Vert \\mathcal{R}_1 \\left( e^{-i\\tau \\sum_{k=1}^L H_k} \\right)- \\mathcal{R}_1 \\left( \\prod_{k=1}^{L} e^{-i H_k \\tau } \\right) \\right \\Vert \\\\\n",
"\\leq& r \\left \\Vert \\mathcal{R}_1 \\left( e^{-i\\tau \\sum_{k=1}^L H_k} \\right) \\right \\Vert+ r\\left \\Vert \\mathcal{R}_1 \\left( \\prod_{k=1}^{L} e^{-i H_k \\tau } \\right) \\right \\Vert \\\\\n",
"\\leq& 2r \\left \\Vert \\mathcal{R}_1 \\left( e^{-i |\\tau | \\sum_{k=1}^L \\Vert H_k \\Vert} \\right) \\right \\Vert \\\\\n",
"\\leq& 2r \\left \\Vert \\mathcal{R}_1 \\left( e^{-i |\\tau | L \\Lambda} \\right) \\right \\Vert \\\\\n",
"\\leq& r ( \\tau L \\Lambda )^2 e^{\\vert \\tau \\vert L \\Lambda } \\\\\n",
"=&\\frac{( t L \\Lambda )^2}{r} e^{\\frac{\\vert t \\vert L \\Lambda}{r} }.\n",
"\\end{aligned}\n",
"\\tag{8}\n",
"$$\n",
"\n",
"The conclusion of linear accumulation of errors in quantum circuits is used here, that is, $\\Vert U^r - V^r \\Vert \\leq r\\Vert U - V \\Vert$. Readers who are not familiar with this conclusion can refer to section 4.5.3 in [3]; and also use the conclusion when $k=1$ in formula (7). So far, we have calculated the upper bound of the simulation error of the product formula for a complete evolution time $t$, that is, the second-order term $O (t^2/r)$ in equation (4).\n",
"\n",
"After obtaining the upper bound of the simulation error, the lower bound of the circuit depth required to reach a certain accuracy $\\epsilon$ can be calculated. From (8), we can find that the formula contains a $L$ term, which means that as the number of Hamiltonian terms increases, the upper bound of simulation error will become larger and larger, which will cause a deeper circuit if we need to control the accuracy. qDRIFT introduced in this tutorial optimizes this problem. qDRIFT focuses on the coefficients of the Hamiltonian itself and models it as a probability distribution. Each unitary gate is sampled from the probability distribution independently and repeated a certain number of times to form a quantum circuit. Finally, under a given accuracy, the depth of the quantum circuits will not explicitly contain the number of Hamiltonian items $L$. Now we will introduce it.\n",
"\n",
"\n",
"## qDRIFT\n",
"\n",
"First, we give the form of the target Hamiltonian\n",
"$$\n",
"H=\\sum_{j=1}^L h_j H_j,\n",
"\\tag{9}\n",
"$$\n",
"\n",
"It contains $L$ sub-Hamiltonian $H_j$, note that here $H_j$ has been normalized, that is, $\\Vert H_j \\Vert = 1$, where $\\Vert\\cdot\\Vert$ is the Schatten-$\\infty$ norm. $h_j$ is the coefficient of each sub-Hamiltonian, which is a positive real number. Using these coefficients, we can construct a discrete probability distribution, and take the proportion of a single coefficient in the sum of the Hamiltonian coefficients as the probability of each unitary gate being sampled, which is $p_j =h_j / \\lambda $, where $\\lambda = \\sum_j h_j $ is the sum of the coefficients. Then the sampling will be repeated $ N $ times (to compared with product formula, we let $N=Lr$ here ), we will get an ordered list arranged by $j $ and can construct a unitary gate $U_j = e^{i\\tau H_j}$ according to the arrangement. Assuming $L = 3 $, $r = 2 $, we can sample an ordered list according to the above probability distribution, as shown in\n",
"\n",
"$$\n",
"[ 3, 1, 2 ,3 ,3 ,1 ],\n",
"$$\n",
"\n",
"then we can construct the quantum circuits as \n",
"\n",
"$$\n",
"U_{circuit} = e^{i\\tau H_1}e^{i\\tau H_3}e^{i\\tau H_3}e^{i\\tau H_2}e^{i\\tau H_1}e^{i\\tau H_3},\n",
"$$\n",
"\n",
"$\\tau = t \\lambda / N$. That is an implementation of qDRIFT to simulate Hamiltonian.\n",
"\n",
"The implementation process of qDRIFT is very simple, and its advantage is that the complexity of the number of unitary gates is $O ((\\lambda t) ^ 2 / \\epsilon) $) when the target precision is $\\epsilon $. It can be seen that this is a result without $L$. In other words, the number of unitary gates is not explicitly related to the number of Hamiltonian terms, which can effectively reduce the length of the simulated circuit when the number of Hamiltonian terms is large. Next, we will give a proof.\n",
"\n",
"We model the process of sampling from probability distribution as a quantum channel. We use the curlicue letters $\\mathcal{E} $ and $\\mathcal{U}$ to represent the channel established through qDRIFT and the channel to be simulated, and use $\\mathcal{E}_ N $ and $\\mathcal{U}_N$ to represent one of the $n $ actions of their respective channels on the quantum state $\\rho $,\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"&\\mathcal{U}_N (\\rho) = e^{\\frac{it}{N}H} \\rho e^{\\frac{-it}{N}H}= e^{\\frac{t}{N}\\mathcal{L}} (\\rho),\n",
"\\\\\n",
"&\\mathcal{E}_N (\\rho)=p_j e^{i\\tau H_j} \\rho e^{-i\\tau H_j}=p_j e^{\\tau \\mathcal{L}_j}(\\rho).\n",
"\\end{aligned}\n",
"\\tag{10}\n",
"$$\n",
"\n",
"Here we introduce the Liouvillian representation, that is, for the quantum channel $\\mathcal{P} (\\rho) = e^{iHt} \\rho e^{-iHt}$, there is\n",
"$$\n",
"\\mathcal{P}(\\rho)=e^{iHt}\\rho e^{-iHt}=e^{t\\mathcal{L}}(\\rho)=\\sum_{k=0}^\\infty \\frac{t^k \\mathcal{L}^k (\\rho)}{k!},\n",
"\\tag{11}\n",
"$$\n",
"\n",
"where $\\mathcal{L} (\\rho) =i (H\\rho - \\rho H) $, similarly, $\\mathcal{L}_ j(\\rho)=i(H_j\\rho - \\rho H_j)$. It should be noted that the operation rules of its series follow $\\mathcal{L}^ {n+1} (\\rho) =i (H\\mathcal{L}^n (\\rho) -\\mathcal{L}^n (\\rho) H) $. Specifically, $\\mathcal{U}_N = \\sum_{n=0}^\\infty \\frac{t^n\\mathcal{L}^n}{n!N^n}$, $\\mathcal{E}_N =\\sum_{j}p_j \\sum_{n=0}^\\infty \\frac{\\lambda^n t^n \\mathcal{L}_j^n}{n!N^n}$. Next, how do we measure the distance between two channels? Here we introduce the definition of [diamond norm](https://en.wikipedia.org/wiki/Diamond_norm)\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"\\Vert \\mathcal{P} \\Vert_\\Diamond :=\\sup_{\\rho ; \\Vert \\rho \\Vert _1 =1}\\Vert (\\mathcal{P} \\otimes \\mathbb{I})(\\rho )\\Vert _1 .\n",
"\\end{aligned}\n",
"\\tag{12}\n",
"$$\n",
"Here $\\mathbb {I} $ is the identity channel which has the same size as $\\mathcal{P}$ and $\\Vert \\cdot \\Vert$ is Schatten-$1$ norm or called [trace norm](https://en.wikipedia.org/wiki/Schatten_norm). We use the diamond norm to define the distance between two quantum channels\n",
"$$\n",
"\\begin{aligned}\n",
"d_\\Diamond (\\mathcal{E},\\mathcal{U}) &=\\frac{1}{2} \\Vert \\mathcal{E} -\\mathcal{U} \\Vert_\\Diamond\n",
"\\\\\n",
"&=\\sup_{\\rho ; \\Vert \\rho \\Vert _1 =1} \\frac{1}{2} \\Vert ((\\mathcal{E}-\\mathcal{U}) \\otimes \\mathbb{I})(\\rho )\\Vert _1 .\n",
"\\end{aligned}\n",
"\\tag{13}\n",
"$$\n",
"The diamond norm represents the maximum possibility that the two channels can be distinguished in all quantum states. The larger its value, the greater the possibility that the two channels can be distinguished, which means that the two channels are far away and the simulation ability is poor; on the contrary, if its value is small, it means that the simulation ability is good. Next, we can calculate the upper bound of the distance of the channel with a single action of the channel\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
" \\Vert \\mathcal{U}_N-\\mathcal{E}_N \\Vert_\\Diamond &= \\left\\Vert \\sum_{n=2}^\\infty \\frac{t^n\\mathcal{L}^n}{n!N^n}-\\sum_{j}\\frac{h_j}{\\lambda} \\sum_{n=2}^\\infty \\frac{\\lambda^n t^n \\mathcal{L}_j^n}{n!N^n} \\right\\Vert_\\Diamond\\\\\n",
" &\\leq \\sum_{n=2}^\\infty \\frac{t^n\\Vert \\mathcal{L}^n \\Vert_\\Diamond }{n!N^n} + \\sum_{j}\\frac{h_j}{\\lambda} \\sum_{n=2}^\\infty \\frac{\\lambda^n t^n \\Vert\\mathcal{L}_j^n \\Vert_\\Diamond }{n!N^n}\\\\\n",
" &\\leq \\sum_{n=2}^\\infty \\frac{1}{n!}\\left( \\frac{2\\lambda t}{N}\\right)^n+\\sum_{j}\\frac{h_j}{\\lambda} \\sum_{n=2}^\\infty \\frac{1}{n!}\\left( \\frac{2\\lambda t}{N}\\right)^n\\\\\n",
" &=2\\sum_{n=2}^\\infty \\frac{1}{n!}\\left( \\frac{2\\lambda t}{N}\\right)^n .\n",
"\\end{aligned}\n",
"\\tag{14}\n",
"$$\n",
"\n",
"The conclusion $\\Vert \\mathcal{L} \\Vert_ \\Diamond \\leq 2\\Vert H\\Vert \\leq 2\\lambda$ is used here. Similarly, $\\Vert \\mathcal{L}_ j \\Vert_ \\Diamond \\leq 2\\Vert H_ j\\Vert \\leq 2$ [4]. Then, we can use the conclusion of (7) mentioned above to make $k=1$, $\\alpha=2 \\lambda t /N$, and then we can get\n",
"\n",
"$$\n",
"d_\\Diamond (\\mathcal{U}_N,\\mathcal{E}_N) \\leq \\frac{2\\lambda^2 t^2}{N^2} e^{2\\lambda t/N} .\n",
"\\tag{15}\n",
"$$\n",
"\n",
"Then using the conclusion of $\\Vert U^r - V^r \\Vert \\leq r\\Vert U - V \\Vert$ again (it should be noted that $U$ and $V$ in the formula are linear operators, but quantum channels $\\mathcal{U}_N$ and $\\mathcal{E}_N$ are still feasible. Refer to Chapter 3.3.2 in [6] to get more details.). With $2\\lambda t \\ll N$ generally, we can deduce\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"d_\\Diamond (\\mathcal{U},\\mathcal{E}) &\\leq N d_\\Diamond (\\mathcal{U}_N, \\mathcal{E}_N)\\\\\n",
" &=\\frac{2\\lambda^2 t^2}{N} e^{2\\lambda t/N} \\approx \\frac{2\\lambda^2 t^2}{N}.\n",
"\\end{aligned}\n",
"\\tag{16}\n",
"$$\n",
"\n",
"So $N \\sim O ((\\lambda t) ^2 /\\epsilon) $. It can be seen from the above formula that under the condition of $\\lambda \\ll \\Lambda L $ (Hamiltonian is defined as $H=\\sum_{j=1}^L h_j H_j$ in qDRIFT, so $\\Lambda = \\max_k h_k $ here), the distance will not be explicitly related to $L$, which means that when $L$ is large, that is, the situation is complex, the quantum circuit will not be increased, and the number of unitary gates can be effectively controlled. Many systems satisfy the condition $\\lambda \\ll \\Lambda L$ such as carbon-dioxide, ethane. But not all cases satisfy. If $\\lambda = \\Lambda L$ or $\\lambda = \\Lambda \\sqrt{L}$, their upper bounds are $O (L^2 (\\Lambda t) ^2 /\\epsilon) $ and $O (L (\\Lambda t) ^2 /\\epsilon) $ respectively, we can see that they still increase with the number of Hamiltonian terms. Interested readers can refer to [4] for more details.\n",
"\n",
"\n",
"## Code Demonstration\n",
"We will implement qDRIFT in combination with code. We will first demonstrate the performance of its sampling results, and then calculate the simulation error of its channel. First, we need to import the required packages."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import warnings\n",
"\n",
"import math\n",
"import numpy as np \n",
"import scipy \n",
"import paddle_quantum as pq \n",
"import paddle\n",
"\n",
"warnings.filterwarnings(\"ignore\") # Hide warnings\n",
"np.set_printoptions(suppress=True,linewidth=np.nan) # Enable full display, so that line breaks would not appear\n",
" # when viewing the matrix on the terminal print\n",
"pq.set_backend('density_matrix') # use density matrix representation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We assume that the system consists of two qubits. We can use the `hamiltonian` module of the Paddle Quantum to construct a Hamiltonian with $L=4$ satisfied $\\lambda \\ll \\Lambda L$ to demonstrate, which is our target Hamiltonian, as follows\n",
"$$\n",
"\\begin{aligned}\n",
"H&=I \\otimes X + 0.05 * X \\otimes Z + 0.05 * I \\otimes Y+0.05 * X \\otimes X .\n",
"\\end{aligned}\n",
"$$"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Target Hamiltonian is :\n",
" [[ 0. +0.j 1. -0.05j 0.05+0.j 0.05+0.j ]\n",
" [ 1. +0.05j 0. +0.j 0.05+0.j -0.05+0.j ]\n",
" [ 0.05+0.j 0.05+0.j 0. +0.j 1. -0.05j]\n",
" [ 0.05+0.j -0.05+0.j 1. +0.05j 0. +0.j ]]\n"
]
}
],
"source": [
"qubits = 2 # Set the number of qubits\n",
"H_j = [(1.0, 'I0,X1'), # The Pauli string of target Hanmiltonian\n",
" (0.05, 'X0,Z1'),\n",
" (0.05, 'I0,Y1'),\n",
" (0.05, 'X0,X1'), ]\n",
"\n",
"H = pq.hamiltonian.Hamiltonian(H_j) \n",
"print(f'Target Hamiltonian is :\\n {H.construct_h_matrix(qubit_num=qubits)}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we calculate probability according to $\\lambda = \\sum_ j h_ j$,$ p_ j=h_j/\\lambda $. In this experiment, we assume that our target accuracy $\\epsilon=0.1$, simulation time $t=1$, that is, we need to sample $n=\\lceil \\frac{2\\lambda^2 t^2} {\\epsilon}\\rceil = 27 $ times."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"To meet the accuracy of 0.1, there are 27 unitary gates needed.\n"
]
}
],
"source": [
"h_j = np.array(H.coefficients) # Get coeffcients\n",
"lamda = h_j.sum()\n",
"p_j = h_j/lamda # Calculate the discrete probability distribution\n",
"accuracy = 0.1\n",
"t = 1\n",
"gate_counts = math.ceil(2 * lamda**2 * t**2 / accuracy)\n",
"\n",
"print(f'To meet the accuracy of {accuracy}, there are {gate_counts} unitary gates needed.')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we will sample 27 times independently from the probability distribution $p_j$ and construct a unitary circuit according to the sample results."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Sample results:\n",
" [1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 4 1]\n",
"The simulation circuit matrix of qDRIFT is: \n",
" [[ 0.51998969-0.02531459j 0.03408183+0.85099285j -0.03524884+0.02376227j -0.0353899 +0.02366261j]\n",
" [-0.03408183+0.85099285j 0.51998969+0.02531459j 0.0353899 +0.02366261j -0.03524884-0.02376227j]\n",
" [-0.03524884+0.02376227j -0.0353899 +0.02366261j 0.51998969-0.02531459j 0.03408183+0.85099285j]\n",
" [ 0.0353899 +0.02366261j -0.03524884-0.02376227j -0.03408183+0.85099285j 0.51998969+0.02531459j]] \n",
"The original circuit matrix is: \n",
" [[ 0.53752508-0.00075235j 0.04202098+0.83966719j -0.04201839+0.04202098j -0.00075235+0.02697398j]\n",
" [-0.04202098+0.83966719j 0.53752508+0.00075235j 0.00075235+0.02697398j -0.04201839-0.04202098j]\n",
" [-0.04201839+0.04202098j -0.00075235+0.02697398j 0.53752508-0.00075235j 0.04202098+0.83966719j]\n",
" [ 0.00075235+0.02697398j -0.04201839-0.04202098j -0.04202098+0.83966719j 0.53752508+0.00075235j]]\n"
]
}
],
"source": [
"np.random.seed(666) # Fix the random seed to demonstrate\n",
"sample_list = np.random.choice(a=range(1, 5), size=gate_counts, replace=True, p=p_j)\n",
"print(f'Sample results:\\n {sample_list}')\n",
"\n",
"# Calculate the sampled unitary circuit according to the sample result\n",
"simulation = np.identity(2 ** qubits) # Generate the identity matrix\n",
"tau = 1j*lamda*t/gate_counts\n",
"for i in sample_list:\n",
" pauli_str_j = (1.0, H_j[i-1][1]) # Get H_ j. Note that its original coefficient should be discarded\n",
" H_i = pq.hamiltonian.Hamiltonian([pauli_str_j]).construct_h_matrix(qubit_num=qubits)\n",
" simulation = np.matmul(scipy.linalg.expm(tau*H_i), simulation) \n",
"origin = scipy.linalg.expm(1j*t*H.construct_h_matrix(qubit_num=qubits)) # Calculate the original circuit of the target Hamiltonian\n",
"print(f'The simulation circuit matrix of qDRIFT is: \\n {simulation} \\nThe original circuit matrix is: \\n {origin}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then we can calculate the simulation error $\\Vert e^{iHt}-U_{circuit} \\Vert $ between the unitary circuit sampled from qDRIFT and the original circuit, note that the norm here is the spectral norm."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Simulation error: 0.0309\n"
]
}
],
"source": [
"distance = 0.5*np.linalg.norm(origin-simulation, ord=2)\n",
"print(f'Simulation error: {distance:.4f}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Of course, we can take a specific quantum state to test. Without losing generality, we assume that the initial quantum state is a zero state, that is, $\\rho(0) = | 0 \\rangle \\langle 0 | $. We can let the quantum state evolve through the original circuit and the simulation circuit respectively. At the time of $t $, the quantum state is $\\rho(t)_{origin}$ and $\\rho(t)_{qDRIFT}$, we can use the fidelity between these two quantum states to measure the effect of simulation circuits."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Initial State: \n",
" [[1.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
" [0.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
" [0.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
" [0.+0.j 0.+0.j 0.+0.j 0.+0.j]]\n",
"The fidelity between two states is 0.9989\n"
]
}
],
"source": [
"rho_0 = pq.state.zero_state(qubits).numpy() # Generate the zero state density matrix\n",
"print(f'Initial State: \\n {rho_0}')\n",
"\n",
"rho_t_origin = pq.state.to_state(origin @ rho_0 @ origin.T.conjugate()) # Evolve through the original circuit\n",
"rho_t_qdrift = pq.state.to_state(simulation @ rho_0 @ simulation.T.conjugate()) # Evolve through the simulation circuit\n",
"fidelity = pq.qinfo.state_fidelity(rho_t_origin, rho_t_qdrift)\n",
"print(f'The fidelity between two states is {float(fidelity):.4f}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It can be found that the above tests meet our accuracy requirements. However, different from a sampled unitary circuit, we regard the qDRIFT sampling method as a quantum channel, that is, a mapping of the quantum state $\\rho $. The above experiment is only a specific instance of this channel. Next, we will analyze the performance of this channel. First, we can define a function to describe the qDRIFT channel."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# Define qDRIFT channel\n",
"def qdrift_channel(iter_num, sample_num, hamiltonian_list, coefficient_list, simulation_time, qubits, input_state):\n",
" '''\n",
" Input :\n",
" iter_num : the current number of iterations, as a label of recursion\n",
" sample_num : number of samples, N\n",
" hamiltonian_list : the list of Pauli string of the target Hamiltonian, H_j\n",
" coefficient_list : the coefficient list of sub-Hamiltonian, h_j\n",
" simulation_time : simulation time, t\n",
" qubits : the number of qubits \n",
" input_state : the input quantum state, which should be a density matrix\n",
" \n",
" Return :\n",
" The quantum state aftre the evolution of this channel, represented in density matrix\n",
" '''\n",
" lamda = coefficient_list.sum() \n",
" tau = lamda*simulation_time/sample_num\n",
" output = 0\n",
"\n",
" if iter_num != 1: # Enable recursion when iteration flag is not 1\n",
" input_state = qdrift_channel(iter_num-1, sample_num, hamiltonian_list,\n",
" coefficient_list, simulation_time, qubits, input_state)\n",
"\n",
" # Calculate e^{iH\\tau} \\rho e^{-iH\\tau} \n",
" for sub_H, sub_h in zip(hamiltonian_list, coefficient_list):\n",
" sub_H = pq.hamiltonian.Hamiltonian([sub_H]).construct_h_matrix(qubit_num=qubits)\n",
" unitary = scipy.linalg.expm(1j*tau*sub_H) # Calculate e^{iH\\tau}\n",
" output += sub_h/lamda*unitary @ input_state @ unitary.conjugate().T\n",
" return output"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then we can calculate the distance between the two channels through the diamond norm, but the solution of the diamond norm is a positive semidefinite programming problem, that is\n",
"\n",
"$$\n",
"d_\\Diamond(\\mathcal{U}- \\mathcal{E})=\\sup_{\\Omega \\geq 0 \\atop \\rho \\geq 0}\\{\\text{Tr}[\\Omega (\\Gamma_\\mathcal{U}-\\Gamma_\\mathcal{E})]: \\Omega \\leq \\rho \\otimes \\mathbb{I},\\text{Tr} (\\rho)=1\\},\n",
"\\tag{17}\n",
"$$\n",
"where $\\Gamma_ \\mathcal{U} $ and $\\Gamma_ \\mathcal{E}$ are Choi representations of the original channel and the simulation channel. There are many forms of positive semidefinite programming and Choi representation of diamond norm, and interested readers can read [6-8] for more details. The Choi representation we use here is:\n",
"$$\n",
"\\Gamma_\\mathcal{P}=\\sum_{i,j=0}^{d-1} |i\\rangle \\langle j| \\otimes \\mathcal{P}(|i\\rangle \\langle j|),\n",
"\\tag{18}\n",
"$$\n",
"where $\\mathcal {P} $ is the quantum channel and $d$ is the dimension of the input quantum state of the quantum channel. Here we first calculate the Choi representation of the two channels."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"# There is how to calculate the Choi representation of the original channel and the qDRIFT channel, \n",
"# and the diamond norm can be calculated under this representation\n",
"choi_qdrift = 0\n",
"choi_origin = 0\n",
"channel = scipy.linalg.expm(1j*t*H.construct_h_matrix(qubit_num=qubits))\n",
"for i in range(2 ** qubits):\n",
" for k in range(2 ** qubits):\n",
" choi_temp = np.zeros((2 ** qubits, 2 ** qubits))\n",
" choi_temp[i][k] = 1 # Generate |i\\rangle \\langle k|\n",
"\n",
" # Calculate the Choi matrix of channel E\n",
" # Calculate \\mathcal{E}(|i\\rangle \\langle k|)\n",
" choi_temp_qdrift = qdrift_channel(gate_counts, gate_counts, H_j, h_j, t, qubits, choi_temp) \n",
" # Calculate |i\\rangle \\langle k| \\otimes \\mathcal{E}(|i\\rangle \\langle k|)\n",
" choi_qdrift += np.kron(choi_temp, choi_temp_qdrift)\n",
"\n",
" # Calculate the Choi matrix of channel U\n",
" # Calculate \\mathcal{U}(|i\\rangle \\langle k|)\n",
" choi_temp_origin = channel @ choi_temp @ channel.T.conjugate()\n",
" # Calculate |i\\rangle \\langle k| \\otimes \\mathcal{U}(|i\\rangle \\langle k|)\n",
" choi_origin += np.kron(choi_temp, choi_temp_origin)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then we can calculate the diamond norm according to formula (17) and find the diamond distance of the two channels. "
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The distance between the two channels is: 0.0764\n"
]
}
],
"source": [
"diamond_distance = 0.5 * pq.qinfo.diamond_norm(paddle.to_tensor(choi_origin-choi_qdrift))\n",
"print(f'The distance between the two channels is: {diamond_distance:.4f}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The calculation results are in line with our expectations. Note that this value represents the expectance of the worst performance of the channel sampling for a specific simulation circuit instance, so it can not guarantee that each sampled circuit can achieve this accuracy."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary\n",
"Quantum simulation itself is a relatively broad topic, and its application is also very extensive. This tutorial introduces the basic theory of product formula and the qDRIFT method, but qDRIFT is not the only method for random product formulas. As a branch of the method of quantum simulation using product formula, random product formula has many methods worth exploring."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Reference\n",
" \n",
"[1] Lloyd, Seth. \"Universal quantum simulators.\" [Science (1996): 1073-1078](https://www.jstor.org/stable/2899535).\n",
"\n",
"[2] Childs, Andrew M., et al. \"Toward the first quantum simulation with quantum speedup.\" [Proceedings of the National Academy of Sciences 115.38 (2018): 9456-9461](https://www.pnas.org/content/115/38/9456.short).\n",
"\n",
"[3] Nielsen, Michael A., and Isaac Chuang. \"Quantum computation and quantum information.\" (2002): 558-559.\n",
"\n",
"[4] Campbell, E. . \"Random Compiler for Fast Hamiltonian Simulation.\" [Physical Review Letters 123.7(2019):070503.1-070503.5](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.070503).\n",
"\n",
"[5] Khatri, Sumeet, and Mark M. Wilde. \"Principles of quantum communication theory: A modern approach.\" [arXiv preprint arXiv:2011.04672 (2020).](https://arxiv.org/abs/2011.04672)\n",
"\n",
"[6] Watrous, J. . [The Theory of Quantum Information](https://cs.uwaterloo.ca/~watrous/TQI/). 2018.\n",
"\n",
"[7] Watrous, J. . \"Simpler semidefinite programs for completely bounded norms.\" [Chicago Journal of Theoretical Computer Science (2012).](https://arxiv.org/abs/1207.5726)\n",
"\n",
"[8] Watrous, J. . \"Semidefinite Programs for Completely Bounded Norms.\" [Theory of Computing 5.1(2009):217-238.](https://arxiv.org/abs/0901.4709)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.7.13 ('py3.7_pq2.2.1')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.13"
},
"vscode": {
"interpreter": {
"hash": "4e4e2eb86ad73936e915e7c7629a18a8ca06348106cf3e66676b9578cb1a47dd"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 量子相位处理\n",
"\n",
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 概览"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**量子相位处理**(quantum phase processing,简称 **QPP**)是一个由百度量子团队 [[1]](https://arxiv.org/abs/2209.14278) 提出的量子算法框架。该框架可以转换和处理酉算子的本征相位,并高效且精准地进行酉算子的本征变换或信息提取。该框架的推出来源于一种名为**三角量子信号处理**(trigonometric quantum signal processing, 简称三角 QSP)技术的改进 [[2]](https://arxiv.org/abs/2205.07848)。该技术可以使用单个比特去模拟输入数据的任意三角变换。对应地,QPP 则在更高维度上继承了三角 QSP 的能力。QPP 通过控制酉算子的方式,获取了酉算子的本征信息,从而在其本征空间下完成了对应本征相位的任意三角变换。\n",
"\n",
"基于论文 [[1]](https://arxiv.org/abs/2209.14278) 和块编码(block encoding)技术,本教程会向大家介绍如何使用 QPP 去解决量子相位估计问题、哈密顿量模拟问题和熵估计问题。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"以下是必要的 libraries 和 packages。"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import warnings\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"import numpy as np\n",
"from scipy.linalg import expm\n",
"import paddle\n",
"\n",
"# 量桨的通用函数\n",
"import paddle_quantum as pq\n",
"from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.hamiltonian import Hamiltonian\n",
"from paddle_quantum.linalg import abs_norm, hermitian_random, unitary_random, block_enc_herm, herm_transform\n",
"from paddle_quantum.qinfo import trace_distance, partial_trace_discontiguous\n",
"from paddle_quantum.state import is_density_matrix, random_state, zero_state, State\n",
"\n",
"# 量桨的 QPP 模块函数\n",
"from paddle_quantum.qpp import Q_generation, hamiltonian_laurent, qpp_angle_approximator, qpp_cir, qps, qubitize, purification_block_enc, simulation_cir\n",
"\n",
"# 设置计算精度和后端\n",
"pq.set_dtype('complex128')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 框架介绍"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"QPP 主要依赖一个名为**相位处理器**的量子线路来处理数据。对于偶数 $L \\in \\mathbb{N}$,我们定义一个 $n$-比特酉算子 $U$ 的相位处理器为\n",
"\n",
"$$\n",
"V^L(U) := R_z^{(0)} R_y^{(0)} R_z^{(0)}\n",
"\\left[ \\prod_{l=1}^{L/2}\n",
" \\begin{bmatrix}\n",
" U^\\dagger & 0 \\\\\n",
" 0 & I^{\\otimes n}\n",
" \\end{bmatrix} R_y^{(0)} R_z^{(0)}\n",
" \\begin{bmatrix}\n",
" I^{\\otimes n} & 0 \\\\\n",
" 0 & U\n",
" \\end{bmatrix} R_y^{(0)} R_z^{(0)}\n",
"\\right],\n",
"\\tag{1}\n",
"$$\n",
"\n",
"这里 $R_y^{(0)}$ 和 $R_z^{(0)}$ 为作用在处理器第一个比特上的旋转门,且其旋转角度取决于模拟的三角多项式。其具体的量子电路如下所示:\n",
"\n",
"![qpp-circuit](figures/QPP-fig-circuit.png \"图 1:相位处理器的量子实现(层数 L 为偶数)\")\n",
"\n",
"注:当层数 $L$ 为奇数时,我们定义相应的相位处理器为\n",
"\n",
"$$ \n",
"V^{L}(U) = V^{L - 1}(U) \n",
" \\begin{bmatrix}\n",
" U^\\dagger & 0 \\\\\n",
" 0 & I^{\\otimes n}\n",
" \\end{bmatrix} R_y^{(0)} R_z^{(0)} \\tag{2}。\n",
"$$\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"QPP 有两个核心功能:**相位演化**与**相位提取**。 具体来说,假设一个有如下谱分解的 $n$-(量子)比特酉矩阵 \n",
"\n",
"$$\n",
"U = \\sum_{j=0}^{2^n - 1} e^{i \\tau_j} |\\chi_j \\rangle \\langle \\chi_j |。 \\tag{3}\n",
"$$\n",
"\n",
"那么论文 [[1]](https://arxiv.org/abs/2209.14278) 中的 **Theorem 5** 证明,对于任意满足 $||\\textbf{c}||_1 \\leq 1$ 的(复数)三角多项式 $F(x) = \\sum_{j = -L}^L c_j e^{ijx}$,和任意量子态 $| \\psi \\rangle = \\sum_{j=0}^{2^n - 1} \\alpha_j |\\chi_j \\rangle$,我们都可以找到一个层数为 $2L$ 的相位处理器 $V^{2L}(U)$ 使得\n",
"\n",
"$$\n",
"\\left( \\langle 0 | \\otimes I^{\\otimes n} \\right) V^{2L}(U) |0, \\psi \\rangle = \\sum_{j=0}^{2^n - 1} \\alpha_j F(\\tau_j) | \\chi_j \\rangle。 \\tag{4}\n",
"$$\n",
"\n",
"进一步地,若 $F$ 的值域为实数域,那么对于任意一个量子态 $\\rho$, 论文 [[1]](https://arxiv.org/abs/2209.14278) 中的 **Theorem 6** 证明,我们可以找到一个层数为 $L$ 的相位处理器 $V^{L}(U)$ 使得\n",
"\n",
"$$\n",
"\\text{Tr} \\left[ Z^{(0)} \\cdot V^{L}(U) \\rho V^{L}(U)^\\dagger \\right] = \\sum_{j = 0}^{2^n - 1} p_j F(\\tau_j), \\tag{5}\n",
"$$\n",
"\n",
"这里 $p_j = \\langle \\chi_j | \\rho | \\chi_j \\rangle$,$Z^{(0)}$ 是作用在第一个比特上的泡利-$Z$ 可观测量。下面我们将用这两个功能来完成酉矩阵的相位搜索、哈密顿量的实时演化和量子态的函数变换的迹估计。\n",
"\n",
"注:由于哈密顿量和量子态不是酉矩阵,无法被 QPP 直接处理,因此我们需要使用**比特化块编码**(qubitized block encoding,下文统一称作块编码)来拿到非酉矩阵的本征信息。\n",
"另外,比特化块编码的本征相位和其编码数据的本征值还存在一个 $\\arccos$ 的关系。所以对于一个函数 $f$ 的变换,QPP 实际需要模拟的函数是 $F(x) = f(\\cos(x))$。\n",
"更多块编码的介绍详见[量子信号处理与量子奇异值变换](https://qml.baidu.com/tutorials/quantum-simulation/quantum-signal-processing-and-quantum-singular-value-transformation.html)教程;比特化的知识可以参考论文 [[1]](https://arxiv.org/abs/2209.14278) 和论文 [[3]](https://quantum-journal.org/papers/q-2019-07-12-163/)。\n",
"\n",
"以下是本教程的环境设置。"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"num_qubits = 3 # 各问题的主寄存器大小\n",
"num_block_qubits = num_qubits + 1 # 使用 block encoding 的辅助比特数\n",
"aux_qubits = list(range(num_block_qubits + 1)) # 辅助寄存器所在的比特索引\n",
"sys_qubits = list(range(num_block_qubits + 1, num_block_qubits + 1 + num_qubits)) # 主寄存器所在的比特索引"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 应用:量子相位搜索"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"求解一个酉算子的本征相位是量子计算的核心问题之一,这个问题也叫做量子相位估计(quantum phase estimation,简称 QPE)。该问题的设定为:已知一个酉算子 $U$ 和它的一个本征态 $| \\psi \\rangle$,求解该本征态所对应的本征相位;进一步地,若 $| \\psi \\rangle$ 不再是 $U$ 的本征态,则转而求解(任意)一个与 $| \\psi \\rangle$ 相交的本征态所对应的本征相位。\n",
"\n",
"以下是该问题的实验设定。"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"pq.set_backend('state_vector') # 切换态矢量后端\n",
"\n",
"U = unitary_random(num_qubits) # 酉算子\n",
"psi = random_state(num_qubits) # 输入态(矢量)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"QPP 可以模拟阶梯函数即\n",
"\n",
"$$\n",
"f(x) = \\begin{cases}\n",
" 1 & \\textrm{if }\\, x \\geq 0 \\\\\n",
" 0 & \\textrm{if }\\, x < 0\n",
"\\end{cases}。 \\tag{6}\n",
"$$\n",
"\n",
"QPP 通过测量辅助比特的方式来二分搜索到一个本征相位所在的小区间;然后将这个区间放大再重复操作,从而完成本征相位的估计任务。我们称该算法为**量子相位搜索**(quantum phase search,简称 QPS)算法。相较于传统 QPE 算法,QPS 算法可以在同等精度和资源下获得成功概率的指数提升。QPS 算法的具体细节可以移步至论文 [[1]](https://arxiv.org/abs/2209.14278) 的第二节。\n",
"\n",
"量桨的 QPP 模块有一个内置函数 `qps`,其实现了完整的 QPS 算法。运行结束后,我们可以将结果与理论值对比来验证算法的正确性。"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Computations of angles for QPP are completed with mean error 1.692667280042757e-07\n",
"估计特征相位与输出态特征相位的匹配误差为 2.104050267612231e-11\n",
"输出态与输入态的 overlap 为 0.12645570235623324\n"
]
}
],
"source": [
"# 使用 QPS 算法获取一个本征相位和主系统的输出态\n",
"phase_estimate, output_state = qps(U, psi)\n",
"\n",
"# 拿到输出态对应的本征相位\n",
"phase_expect = np.log((output_state.bra @ U @ output_state.ket).item()) / 1j\n",
"\n",
"print(f\"估计本征相位与输出态本征相位的匹配误差为 {np.abs(phase_expect - phase_estimate)}\")\n",
"print(f\"输出态与输入态的 overlap 为 {abs_norm(output_state.bra @ psi.ket)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"由上可见,虽然输入态 $| \\psi \\rangle$ 不是 $U$ 的本征态,我们仍可以拿到与 $| \\psi \\rangle$ 内积不为 $0$ 的本征态对应的本征相位。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 应用:哈密顿量模拟"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"一个随着时间演化的 $n$-比特量子系统可以由一个哈密顿量 $H$ 和这个量子系统的初始态 $\\rho$ 决定。该系统在时间 $t$ 的量子态可以表示为 $\\rho_t = e^{-iHt} \\rho e^{iHt}$,这里 $e^{-iHt}$ 称为该系统在时刻 $t$ 下的演化算子。那么哈密顿量模拟的问题设定为:已知一个量子系统的哈密顿量的块编码 $U$ 和初始态 $\\rho$,近似制备该系统在时刻 $t$ 下的量子态 $\\rho_t$。\n",
"\n",
"以下是该问题的实验设定。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"pq.set_backend(\"density_matrix\") # 切换密度矩阵后端\n",
"\n",
"H = hermitian_random(num_qubits) # 量子系统的哈密顿量\n",
"U = block_enc_herm(H, num_block_qubits) # 哈密顿量的比特化块编码\n",
"rho = random_state(num_qubits) # 初始态(密度矩阵)\n",
"t = 9 # 演化时间\n",
"L = 40 # 相位处理器的层数,即模拟演化函数的精度"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"量桨 QPP 模块的 `hamiltonian_laurent` 可以通过 [Jacobi-Anger 展开](https://en.wikipedia.org/wiki/Jacobi%E2%80%93Anger_expansion) 提供演化函数的近似三角多项式 $P$;然后用 `Q_generation` 算出该多项式的余式 $Q$(两者满足 $PP^* + QQ^* = 1$);函数 `qpp_angle_approximator` 负责估算相位处理器的所有旋转门角度;最后函数 `qpp_cir` 完成相位处理器的构建。"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Computations of angles for QPP are completed with mean error 1.1413227206232605e-07\n"
]
}
],
"source": [
"# 准备近似模拟演化函数的三角多项式和其余式, 这里乘个略小于 1 的数来保证余式的成功计算\n",
"P = hamiltonian_laurent(-t, L) * 0.999999\n",
"Q = Q_generation(P)\n",
"\n",
"# 计算旋转角度,其中 theta 对应 Ry 门,phi 对应 Rz 门\n",
"list_theta, list_phi = qpp_angle_approximator(P, Q)\n",
"\n",
"cir = qpp_cir(list_theta, list_phi, U) # 构建相位处理器\n",
"cir.collapse(aux_qubits, desired_result=0, if_print=True) # 在线路末端添加解码(测量)操作\n",
"input_state = zero_state(num_block_qubits + 1).kron(rho) # 准备输入态,其中辅助比特的输入态为 |0><0|"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"qubits [0, 1, 2, 3, 4] collapse to the state |00000> with probability 0.9999974132814917\n",
"输出态与期望态的迹距离为 4.903932274526325e-07\n"
]
}
],
"source": [
"# 拿到输出态并移除辅助比特,将输出态与理论值对比\n",
"output_state = partial_trace_discontiguous(cir(input_state), preserve_qubits=sys_qubits)\n",
"rho.evolve(H, t)\n",
"print(f\"输出态与期望态的迹距离为 {trace_distance(output_state, rho).item()}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"由上可见,QPP 通过相位演化,将一个 $H$ 的块编码转换为 $e^{-iHt}$ 的块编码,从而完成量子态 $\\rho_t$ 的成功制备。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 应用:量子态熵估计"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"如何计算量子态函数变换后的迹是量子熵估计的核心问题。该问题的数学设定为:已知两个量子态 $\\rho$、 $\\sigma$ 和一个函数 $f:~\\mathbb{R}_+~\\to~\\mathbb{R}$,估计 $\\text{Tr}\\left[ \\rho f \\left( \\sigma \\right) \\right]$。\n",
"\n",
"注:量子态 $\\sigma$ 的 $f$ 变换定义为 \n",
"\n",
"$$ \n",
"f(\\sigma) = \\sum_j f(q_j) | \\psi_j \\rangle \\langle \\psi_j |, \\tag{7}\n",
"$$\n",
"这里 $\\{q_j\\}$ 和 $\\{ | \\psi_j \\rangle \\}$ 分别为 $\\sigma$ 的本征值和本征态。 \n",
"\n",
"可以看到,若 $f$ 为幂函数,该问题可以用来解决量子瑞丽熵和瑞丽散度的估计;而当 $f(x) = \\log(x)$ 时,该问题则转化为量子冯诺依曼熵或相对熵的估计问题。\n",
"\n",
"QPP 的相位提取功能可以有效地解决迹估计问题,从而进一步地完成各类量子熵的近似计算。用于处理量子态的 QPP 线路如图所示:\n",
"\n",
"![qpp](figures/QPP-fig-state.png \"图 2:用于迹估计的相位处理器(层数为偶数),这里 m 为用于块编码的辅助比特数。\")\n",
"\n",
"这里作用在 AB 系统上的 $U_\\rho$ 为 $\\rho$ 的**纯化模型**,满足\n",
"\n",
"$$\n",
"\\text{Tr}_B \\left[ U_\\rho \\left( | 0 \\rangle_A \\langle 0 |_A \\otimes | 0 \\rangle_B \\langle 0 |_B \\right) U_\\rho^\\dagger \\right] = \\rho。 \\tag{8}\n",
"$$\n",
"\n",
"该模型可以用量子电路实现,所以在量子熵的研究中被广泛使用。$U_\\sigma$ 也为 $\\sigma$ 的纯化模型。不同的是,为了拿到 $\\sigma$ 的本征信息,QPP 需要将 $U_\\sigma$ 进一步转为 $\\sigma$ 的块编码 $\\widehat{U}_\\sigma$。更多内容详见论文 [[1]](https://arxiv.org/abs/2209.14278) 的第四节。\n",
"\n",
"我们将使用 QPP 模块来估计 $\\text{Tr} \\left[ \\rho \\sigma^{\\alpha - 1} \\right]$。以下是该问题的实验设定。"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"pq.set_backend(\"density_matrix\") # 切换密度矩阵后端\n",
"\n",
"rho = random_state(num_qubits) # 输入态(密度矩阵),这里我们不关注上图的 B 系统\n",
"U_sigma_hat = purification_block_enc(num_qubits, num_block_qubits) # 构建一个纯化模型的比特化块编码\n",
"sigma = State(U_sigma_hat[:2**num_qubits, :2**num_qubits]) # 通过块编码获取随机 sigma\n",
"assert is_density_matrix(sigma) == (True, num_qubits)\n",
"\n",
"alpha = np.random.rand() * 4 + 1 # 在 [1, 5) 之间随机选择 alpha\n",
"H = Hamiltonian([(1.0, \"z0\")]) # 第一个比特上的泡利-Z 可观测量\n",
"input_state = zero_state(num_block_qubits + 1).kron(rho) # 相位处理器的输入态"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"量桨 QPP 模块里的 `simulation_cir` 函数可以为 $f$ 设计适应性的相位处理器。具体地,通过找到函数 $f$ 的近似三角多项式 $F$,QPP 模块可以在机械误差下将 $F$ 转为相位处理器需要模拟的三角多项式,然后通过估算第一个比特上泡利-$Z$ 可观测量的期望值来获取最终的结果。我们可以借助 `paddle_quantum.linalg.herm_transform` 来评估模拟的精准度。\n",
"\n",
"注:函数 $f$ 需要满足 $f\\left(\\left[0, 1\\right]\\right) \\subseteq \\left[-1, 1\\right]$。"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Computations of angles for QPP are completed with mean error 2.073248366556774e-07\n",
"模拟 Tr[rho * sigma^3.522308957539911] 的误差为 2.265239344059755e-05\n"
]
}
],
"source": [
"# 估计 Tr[rho * sigma^(alpha - 1)]\n",
"cir = simulation_cir(lambda x: (np.cos(x) ** 2) ** ((alpha - 1) / 2), U_sigma_hat)\n",
"val = cir(input_state).expec_val(H).item()\n",
"expect_val = paddle.trace(rho.data @ herm_transform(lambda x: x ** (alpha - 1), sigma)).real().item()\n",
"print(f\"模拟 Tr[rho * sigma^{alpha - 1}] 的误差为 {np.abs(val - expect_val)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"由上可见,QPP 通过相位提取来完成 $\\text{Tr} \\left[ \\rho \\sigma^{\\alpha - 1} \\right]$ 的精确估计。因此,当 $\\rho = \\sigma$ 时,QPP 就可以估算 $\\rho$ 的 $\\alpha$-阶量子瑞丽熵 \n",
"\n",
"$$\n",
"S_\\alpha(\\rho) = \\frac{1}{1 - \\alpha}\\log \\text{Tr} \\left( \\rho^{\\alpha } \\right)。 \\tag{9}\n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 小结\n",
"\n",
"通过上述三个应用,本教程展示了 QPP 框架在酉算子、哈密顿量和量子态相关问题上的强大处理能力,以及如何用量桨的 QPP 模块去完成框架的构建和运算。我们也期望使用 QPP 框架去解决更多问题包括但不限于量子蒙特卡洛问题、酉矩阵迹估计问题以及机器学习问题。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"___\n",
"## 参考文献\n",
"\n",
"[1] Wang, Xin, et al. \"Quantum Phase Processing: Transform and Extract Eigen-Information of Quantum Systems.\" [arXiv preprint arXiv:2209.14278 (2022).](https://arxiv.org/abs/2209.14278)\n",
"\n",
"[2] Yu, Zhan, et al. \"Power and limitations of single-qubit native quantum neural networks.\" [arXiv preprint arXiv:2205.07848 (2022).](https://arxiv.org/abs/2205.07848)\n",
"\n",
"[3] Low, Guang Hao, and Isaac L. Chuang. \"Hamiltonian simulation by qubitization.\" [Quantum 3 (2019): 163.](https://quantum-journal.org/papers/q-2019-07-12-163/)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.13 ('pq')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.13"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "08942b1340a5932ff3a93f52933a99b0e263568f3aace1d262ffa4d9a0f2da31"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Quantum Phase Processing\n",
"\n",
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Overview"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Quantum Phase Processing** (QPP) is a quantum algorithmic structure purposed by the Baidu Research team [[1]](https://arxiv.org/abs/2209.14278). Such structure can provide access to the eigenphases of the target unitary, allowing phase transformation or extraction to be done in an efficient and precise manner. QPP originates from an improved technique known as **Trigonometric Quantum Signal Processing** (namely the trigonometric QSP) [[2]](https://arxiv.org/abs/2205.07848) that can simulate arbitrary trigonometric transformation of input data using only one qubit. Consequently, QPP inherits the capability of trigonometric QSP in a higher dimension. By manipulating the input unitary, QPP can retrieve its eigen-information and hence perform trigonometric transformation to each eigenphase insider the corresponding eigenspace.\n",
"\n",
"This tutorial will illustrate how to utilize QPP to resolve the problems of quantum phase estimation, Hamiltonian simulation and entropy estimation, according to the paper [[1]](https://arxiv.org/abs/2209.14278) and the idea of block encoding."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here are some necessary libraries and packages."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import warnings\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"import numpy as np\n",
"from scipy.linalg import expm\n",
"import paddle\n",
"\n",
"# general functions in PaddleQuantum\n",
"import paddle_quantum as pq\n",
"from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.hamiltonian import Hamiltonian\n",
"from paddle_quantum.linalg import abs_norm, hermitian_random, unitary_random, block_enc_herm, herm_transform\n",
"from paddle_quantum.qinfo import trace_distance, partial_trace_discontiguous\n",
"from paddle_quantum.state import is_density_matrix, random_state, zero_state, State\n",
"\n",
"# functions in QPP module\n",
"from paddle_quantum.qpp import Q_generation, hamiltonian_laurent, qpp_angle_approximator, qpp_cir, qps, qubitize, purification_block_enc, simulation_cir\n",
"\n",
"# set the precision\n",
"pq.set_dtype('complex128')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Introduction of QPP Structure"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"QPP relies on a quantum circuit namely the **quantum phase processor** to deal with quantum data. For even $L \\in \\mathbb{N}$, we define the quantum phase processor of an $n$-qubit unitary $U$ as\n",
"\n",
"$$\n",
"V^L(U) := R_z^{(0)} R_y^{(0)} R_z^{(0)}\n",
"\\left[ \\prod_{l=1}^{L/2}\n",
" \\begin{bmatrix}\n",
" U^\\dagger & 0 \\\\\n",
" 0 & I^{\\otimes n}\n",
" \\end{bmatrix} R_y^{(0)} R_z^{(0)}\n",
" \\begin{bmatrix}\n",
" I^{\\otimes n} & 0 \\\\\n",
" 0 & U\n",
" \\end{bmatrix} R_y^{(0)} R_z^{(0)}\n",
"\\right],\n",
"\\tag{1}\n",
"$$\n",
"\n",
"Here $R_y^{(0)}$ and $R_z^{(0)}$ are rotation gates applied on the first qubit with tunable parameters depending on the target trigonometric polynomial. The quantum circuit of $V^L(U)$ is shown as follows:\n",
"\n",
"![qpp_circuit](figures/QPP-fig-circuit.png \"Figure 1:Quantum implementation for phase processor with even L\")\n",
"\n",
"Note:when the number of layers $L$ is odd, we define the corresponding phase processor as\n",
"\n",
"$$\n",
"V^{L}(U) = V^{L - 1}(U) \n",
" \\begin{bmatrix}\n",
" U^\\dagger & 0 \\\\\n",
" 0 & I^{\\otimes n}\n",
" \\end{bmatrix} R_y^{(0)} R_z^{(0)}. \\tag{2}\n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"QPP has two main functionals: **phase evolution** and **phase estimation**. Suppose we have an $n$-qubit unitary with the following spectrum decomposition\n",
"\n",
"$$\n",
"U = \\sum_{j=0}^{2^n - 1} e^{i \\tau_j} |\\chi_j \\rangle \\langle \\chi_j |. \\tag{3}\n",
"$$\n",
"\n",
"Then by **Theorem 5** in [[1]](https://arxiv.org/abs/2209.14278), for any (complex) trigonometric polynomial $F(x) = \\sum_{j = -L}^L c_j e^{ijx}$ such that $||\\textbf{c}||_1 \\leq 1$, and any quantum state $| \\psi \\rangle = \\sum_{j=0}^{2^n - 1} \\alpha_j |\\chi_j \\rangle$, there exists a quantum phase processor $V^{2L}(U)$ of $2L$ layers such that\n",
"\n",
"$$\n",
"\\left( \\langle 0 | \\otimes I^{\\otimes n} \\right) V^{2L}(U) |0, \\psi \\rangle = \\sum_{j=0}^{2^n - 1} \\alpha_j F(\\tau_j) | \\chi_j \\rangle. \\tag{4}\n",
"$$\n",
"\n",
"Further, suppose $F$ is real-valued. Then for any state $\\rho$, by **Theorem 6** in [[1]](https://arxiv.org/abs/2209.14278) there exists a quantum phase processor $V^{L}(U)$ of $L$ layers\n",
"\n",
"$$\n",
"\\text{Tr} \\left[ Z^{(0)} \\cdot V^{L}(U) \\rho V^{L}(U)^\\dagger \\right] = \\sum_{j = 0}^{2^n - 1} p_j F(\\tau_j), \\tag{5}\n",
"$$\n",
"\n",
"where $p_j = \\langle \\chi_j | \\rho | \\chi_j \\rangle$ and $Z^{(0)}$ is the Pauli-$Z$ observable acting on the first qubit. In this tutorial we wil use these two abilities to complete the eigenphase search of unitary, the real-time evolution of Hamiltonian and the trace estimation of function transformation of quantum states.\n",
"\n",
"Note: Since Hamiltonian and quantum states are not unitaries, and hence cannot be processed by QPP directly. Therefore, we need to use the **qubitized block encoding** to get the eigen-information of non-unitary data.\n",
"Besides, there is an $\\arccos$ relation between eigenphases of qubitized block encoding and eigenvalues of its encoded data. Then to perform a transformation of function $f$, QPP needs to simulate $F(x) = f(\\cos(x))$.\n",
"\n",
"See more details of block encoding in the tutorial of [Quantum Signal Processing and Quantum Singular Value Transformation](https://qml.baidu.com/tutorials/quantum-simulation/quantum-signal-processing-and-quantum-singular-value-transformation.html). The idea of qubitized block encoding is deferred to [[1]](https://arxiv.org/abs/2209.14278) and [[3]](https://quantum-journal.org/papers/q-2019-07-12-163/).\n",
" \n",
"\n",
"Here is the environment setup of this tutorial."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"num_qubits = 3 # size of main register\n",
"num_block_qubits = num_qubits + 1 # number of ancilla qubits used in block encoding\n",
"aux_qubits = list(range(num_block_qubits + 1)) # qubit indexes for auxiliary register\n",
"sys_qubits = list(range(num_block_qubits + 1, num_block_qubits + 1 + num_qubits)) # qubit indexes for main register"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Application: Quantum Phase Search"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finding eigenphases of a unitary is one of the fundamental issues in quantum computation, namely the problem of quantum phase estimation. The setup of this problem is as follows: given a unitary $U$ and its eigenstate $| \\psi \\rangle$, estimate the eigenphase corresponding to $| \\psi \\rangle$; further, if $| \\psi \\rangle$ is no longer an eigenstate of $U$, then estimate the eigenphase corresponding to a eigenstate having non-zero overlap with $| \\psi \\rangle$.\n",
"\n",
"Here is the experimental setup of this problem."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"pq.set_backend('state_vector') # switch to state vector backend\n",
"\n",
"U = unitary_random(num_qubits) # input unitary\n",
"psi = random_state(num_qubits) # input state (vector)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"QPP can simulate ladder function i.e.\n",
"\n",
"$$\n",
"f(x) = \\begin{cases}\n",
" 1 & \\textrm{if }\\, x \\geq 0 \\\\\n",
" 0 & \\textrm{if }\\, x < 0\n",
"\\end{cases}. \\tag{6}\n",
"$$\n",
"\n",
"Through indirect measurements, binary searches are applied to locate a small interval containing an eigenphase of $U$; this interval is then expanded such that binary search can be used again. Such method is called the **quantum phase search** (QPS) algorithm. Compared with conventional QPE algorithm, QPS can achieve exponential enhancement on the success probability, under same precision and resource usage. Details of QPS is deferred to section 2 of paper [[1]](https://arxiv.org/abs/2209.14278).\n",
"\n",
"The QPP module of Paddle Quantum has a built-in function `qps` that can realize the QPS algorithm. We can compare the experimental result with theoretical one to verify the correction of this method."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Computations of angles for QPP are completed with mean error 1.3084730192565284e-07\n",
"The searching error for the QPS algorithm is 1.4529266689268915e-11\n",
"The overlap between input and output states is 0.6462206013583379\n"
]
}
],
"source": [
"# Use QPS algorithm to retrieve an eigenphase and its output state\n",
"phase_estimate, output_state = qps(U, psi)\n",
"\n",
"# Compute the eigenphase corresponding to the output state\n",
"phase_expect = np.log((output_state.bra @ U @ output_state.ket).item()) / 1j\n",
"\n",
"print(f\"The searching error for the QPS algorithm is {np.abs(phase_expect - phase_estimate)}\")\n",
"print(f\"The overlap between input and output states is {abs_norm(output_state.bra @ psi.ket)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As shown above, despite that $| \\psi \\rangle$ is not an eigenstate of $U$, we can still find an eigenphase and its corresponding eigenstate having non-zero overlap with $| \\psi \\rangle$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Application: Hamiltonian Simulation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The time evolution of an $n$-qubit quantum system is decided by a Hamiltonian $H$ (a $2^n \\times 2^n$ Hermitian matrix acting on $n$ qubits) and an initial state $\\rho$. The quantum state of this system at time $t$ can be expressed as $\\rho_t = e^{-iHt} \\rho e^{iHt}$, where $e^{-iHt}$ is the evolution operator at time $t$. Then the problem of Hamiltonian simulation can be formulated as follows: given a block encoding of the Hamiltonian of a quantum system and its initial state $\\rho$, prepare the quantum state $\\rho_t$ of this system at time $t$.\n",
"\n",
"Here is the experimental setup of this problem."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"pq.set_backend(\"density_matrix\") # switch to density matrix backend\n",
"\n",
"H = hermitian_random(num_qubits) # Hamiltonian of quantum system\n",
"U = block_enc_herm(H, num_block_qubits) # qubitized block encoding of the Hamiltonian\n",
"rho = random_state(num_qubits) # initial state\n",
"t = 9 # evolution time\n",
"L = 40 # number of layers of quantum phase processor i.e. degree of simulation precision"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In QPP module, from the [Jacobi-Anger expansion](https://en.wikipedia.org/wiki/Jacobi%E2%80%93Anger_expansion), function `hamiltonian_laurent` can provide a trigonometric polynomial $P$ approximating the evolution function. Then we can use `Q_generation` to calculate its polynomial complement $Q$ (that satisfies $PP^* + QQ^* = 1$). After estimating the angles for rotation gates by `qpp_angle_approximator`, `qpp_cir` will eventually give a quantum realization of the quantum phase processor."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Computations of angles for QPP are completed with mean error 3.6991674515121774e-07\n"
]
}
],
"source": [
"# Prepare the trigonometric poly approximating the evolution function, and its complement;\n",
"# here we multiply P by constant slightly smaller than 1 to ensure the computation of Q\n",
"P = hamiltonian_laurent(-t, L) * 0.999999\n",
"Q = Q_generation(P)\n",
"\n",
"# Compute the rotation angles, where theta/phi corresponds to Ry/Rz gates\n",
"list_theta, list_phi = qpp_angle_approximator(P, Q)\n",
"\n",
"cir = qpp_cir(list_theta, list_phi, U) # construct quantum phase processor构建相位处理器\n",
"cir.collapse(aux_qubits, desired_result=0, if_print=True) # decoding (measurement) of the block encoding \n",
"input_state = zero_state(num_block_qubits + 1).kron(rho) # prepare the input state, where input state for aux reg is |0><0|"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"qubits [0, 1, 2, 3, 4] collapse to the state |00000> with probability 0.9999982182557698\n",
"The trace distance between output and expected states is 5.304595279602356e-07\n"
]
}
],
"source": [
"# Get the output state, remove the aux qubits, and compare the output state with the expected one\n",
"output_state = partial_trace_discontiguous(cir(input_state), preserve_qubits=sys_qubits)\n",
"rho.evolve(H, t)\n",
"print(f\"The trace distance between output and expected states is {trace_distance(output_state, rho).item()}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As shown above, through phase evolution, QPP transform a block encoding of $H$ to a block encoding of $e^{-iHt}$, so that $\\rho_t$ is successfully prepared."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Application: Estimation of Quantum Entropies"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Trace computation of function transformation of quantum states is the core component of quantum entropy estimation. The mathematical setting of this problem is as follows: given two quantum states $\\rho, \\sigma$ and a function $f:~\\mathbb{R}_+~\\to~\\mathbb{R}$, estimate the quantity $\\text{Tr}\\left[ \\rho f \\left( \\sigma \\right) \\right]$. \n",
"\n",
"Note:$f$ transformation of quantum state $\\sigma$ is defined as \n",
"\n",
"$$ \n",
"f(\\sigma) = \\sum_j f(q_j) | \\psi_j \\rangle \\langle \\psi_j |, \\tag{7}\n",
"$$\n",
"\n",
"where $\\{q_j\\}$ and $\\{ | \\psi_j \\rangle \\}$ are eigenvalues and eigenstates of $\\sigma$, respectively.\n",
"\n",
"We can observe that, if $f$ is a power function, then the solution of this problem can estimate quantum Rényi entropies and Rényi divergences; if $f(x) = \\log(x)$, such problem is equivalent to the estimation of quantum Von Neumann or relative entropies.\n",
"\n",
"QPP can efficiently solve the trace estimation problem by phase estimation, so that serval quantum entropies can be estimated. The quantum circuit is shown as follows:\n",
"\n",
"![qpp](figures/QPP-fig-state.png \"Figure 2: quantum phase processor used for trace estimation. Here m is the number of ancilla qubits for block encoding.\")\n",
"\n",
"Here $U_\\rho$ acting on AB systems is the **purification model** of $\\rho$, satisfying\n",
"\n",
"$$\n",
"\\text{Tr}_B \\left[ U_\\rho \\left( | 0 \\rangle_A \\langle 0 |_A \\otimes | 0 \\rangle_B \\langle 0 |_B \\right) U_\\rho^\\dagger \\right] = \\rho. \\tag{8}\n",
"$$\n",
"\n",
"Such model can be realized by quantum realized, and are frequently used in the study of quantum entropies. $U_\\sigma$ is also the purification model of $\\sigma$. However, to obtain the eigen-information of $\\sigma$, QPP further transforms $U_\\sigma$ to the block encoding $\\widehat{U}_\\sigma$ of $\\sigma$. See more details in section 4 of paper [[1]](https://arxiv.org/abs/2209.14278).\n",
"\n",
"We will use above circuit to estimate $\\text{Tr} \\left[ \\rho \\sigma^{\\alpha - 1} \\right]$. Here is the experimental setup of this problem."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"pq.set_backend(\"density_matrix\") # switch to density matrix backend\n",
"\n",
"rho = random_state(num_qubits) # quantum state rho, here we don't care about the system B\n",
"U_sigma_hat = purification_block_enc(num_qubits, num_block_qubits) # construct a qubitized block encoding of (random) purification model\n",
"sigma = State(U_sigma_hat[:2**num_qubits, :2**num_qubits]) # obtain sigma by block encoding\n",
"assert is_density_matrix(sigma) == (True, num_qubits)\n",
"\n",
"alpha = np.random.rand() * 4 + 1 # randomly select alpha in [1, 5)\n",
"H = Hamiltonian([(1.0, \"z0\")]) # Pauli-Z observable acting on the first qubit\n",
"input_state = zero_state(num_block_qubits + 1).kron(rho) # input state for the phase processor"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The built-in function `simulation_cir` in QPP module can design adaptive phase processor for function $f$. In particular, by finding the trigonometric polynomial $F$ approximating the function $f$. QPP module can transform $F$ to the trigonometric polynomial that phase processor needs to simulate, under machinery error. Then the final result can be obtained by computing the expectation value of Pauli-Z observable acting on the first qubit. We can utilize `paddle_quantum.linalg.herm_transform` to assess the simulation precision.\n",
"\n",
"Note: function $f$ needs to satisfy $f\\left(\\left[0, 1\\right]\\right) \\subseteq \\left[-1, 1\\right]$."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Computations of angles for QPP are completed with mean error 4.448551001234416e-06\n",
"The estimation error for Tr[rho * sigma^1.4747437820674594] is 0.0022607517544651345\n"
]
}
],
"source": [
"# Estimate Tr[rho * sigma^(alpha - 1)]\n",
"cir = simulation_cir(lambda x: (np.cos(x) ** 2) ** ((alpha - 1) / 2), U_sigma_hat)\n",
"val = cir(input_state).expec_val(H).item()\n",
"expect_val = paddle.trace(rho.data @ herm_transform(lambda x: x ** (alpha - 1), sigma)).real().item()\n",
"print(f\"The estimation error for Tr[rho * sigma^{alpha - 1}] is {np.abs(val - expect_val)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As shown above, through phase estimation, QPP can precisely estimate $\\text{Tr} \\left[ \\rho \\sigma^{\\alpha - 1} \\right]$. Therefore, when $\\rho = \\sigma$, QPP is capable to estimate the $\\alpha$-Rényi entropy of $\\rho$, defined as\n",
"\n",
"$$\n",
"S_\\alpha(\\rho) = \\frac{1}{1 - \\alpha}\\log \\text{Tr} \\left( \\rho^{\\alpha } \\right). \\tag{9}\n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusion\n",
"\n",
"Through above applications, this tutorial demonstrates that QPP structure is capable of solving problems of unitaries, Hamiltonians and quantum states. Other than applications mentioned in this tutorial, we expect to use such framework to QPP can be potentially applied to other problems, including but not limited to problems of quantum Monte Carlo, unitary trace estimation and machine learning."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"___\n",
"## Reference\n",
"\n",
"[1] Wang, Xin, et al. \"Quantum Phase Processing: Transform and Extract Eigen-Information of Quantum Systems.\" [arXiv preprint arXiv:2209.14278 (2022).](https://arxiv.org/abs/2209.14278)\n",
"\n",
"[2] Yu, Zhan, et al. \"Power and limitations of single-qubit native quantum neural networks.\" [arXiv preprint arXiv:2205.07848 (2022).](https://arxiv.org/abs/2205.07848)\n",
"\n",
"[3] Low, Guang Hao, and Isaac L. Chuang. \"Hamiltonian simulation by qubitization.\" [Quantum 3 (2019): 163.](https://quantum-journal.org/papers/q-2019-07-12-163/)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.13 ('pq')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.13"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "08942b1340a5932ff3a93f52933a99b0e263568f3aace1d262ffa4d9a0f2da31"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 17,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -116,16 +116,9 @@ ...@@ -116,16 +116,9 @@
"当 $A$ 是酉矩阵时,我们则可以选择 $U = A \\oplus I^{\\otimes (m - n)}$,即 $U$ 是受控的 $A$。特别地,文献[[3]](http://arxiv.org/abs/2203.10236) [[4]](http://arxiv.org/abs/2206.03505)给出了一些使用量子电路实现块编码的方案。" "当 $A$ 是酉矩阵时,我们则可以选择 $U = A \\oplus I^{\\otimes (m - n)}$,即 $U$ 是受控的 $A$。特别地,文献[[3]](http://arxiv.org/abs/2203.10236) [[4]](http://arxiv.org/abs/2206.03505)给出了一些使用量子电路实现块编码的方案。"
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 18,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -161,23 +154,16 @@ ...@@ -161,23 +154,16 @@
"解码的成功概率取决于测量到 $0^{\\otimes (m - n)}$ 的几率,其会随着 $m - n$ 的增长而指数性下降。然而,若我们可以控制 $m - n$ 的大小,那么这就不再是一个问题。在本教程中我们会假设 $m = n + 1$." "解码的成功概率取决于测量到 $0^{\\otimes (m - n)}$ 的几率,其会随着 $m - n$ 的增长而指数性下降。然而,若我们可以控制 $m - n$ 的大小,那么这就不再是一个问题。在本教程中我们会假设 $m = n + 1$."
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 19,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"qubits [0] collapse to the state |0> with probability 0.08025970130578351\n" "qubits [0] collapse to the state |0> with probability 0.24548491835594177\n"
] ]
} }
], ],
...@@ -191,11 +177,11 @@ ...@@ -191,11 +177,11 @@
"input_state = paddle_quantum.State(paddle.kron(zero_state @ dagger(zero_state), rho))\n", "input_state = paddle_quantum.State(paddle.kron(zero_state @ dagger(zero_state), rho))\n",
"\n", "\n",
"# 定义辅助寄存器\n", "# 定义辅助寄存器\n",
"aux_register = [i for i in range(num_qubits - num_block_qubits)]\n", "aux_register = list(range(num_qubits - num_block_qubits))\n",
"\n", "\n",
"# 创建线路\n", "# 创建线路\n",
"cir = Circuit(num_qubits)\n", "cir = Circuit(num_qubits)\n",
"cir.oracle(U, [i for i in range(num_qubits)])\n", "cir.oracle(U, list(range(num_qubits)))\n",
"cir.collapse(aux_register, desired_result='0', if_print = True) # 调用塌缩算子\n", "cir.collapse(aux_register, desired_result='0', if_print = True) # 调用塌缩算子\n",
"\n", "\n",
"# 获取输出态及输出 rho\n", "# 获取输出态及输出 rho\n",
...@@ -212,14 +198,14 @@ ...@@ -212,14 +198,14 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 20,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"期望 rho 和 输出 rho 的误差是 8.008999270740103e-08\n" "期望 rho 和 输出 rho 的误差是 0.0\n"
] ]
} }
], ],
...@@ -229,13 +215,6 @@ ...@@ -229,13 +215,6 @@
"print(f\"期望 rho 和 输出 rho 的误差是 {paddle.norm(paddle.abs(expect_rho - output_rho)).item()}\")" "print(f\"期望 rho 和 输出 rho 的误差是 {paddle.norm(paddle.abs(expect_rho - output_rho)).item()}\")"
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 特例 ($m = 1$)"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
...@@ -283,7 +262,7 @@ ...@@ -283,7 +262,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 21,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -306,13 +285,6 @@ ...@@ -306,13 +285,6 @@
"值得注意的是,$W_\\Phi$ 可以模拟切比雪夫多项式变换。对于次数为 $k$ 的第一类切比雪夫多项式,其对应的角度矢量为 $(0, \\pi, ..., \\pi)$ (如 $k$ 为偶数)或 $(\\pi, ..., \\pi)$ (如 $k$ 为奇数)." "值得注意的是,$W_\\Phi$ 可以模拟切比雪夫多项式变换。对于次数为 $k$ 的第一类切比雪夫多项式,其对应的角度矢量为 $(0, \\pi, ..., \\pi)$ (如 $k$ 为偶数)或 $(\\pi, ..., \\pi)$ (如 $k$ 为奇数)."
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
...@@ -322,7 +294,7 @@ ...@@ -322,7 +294,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 22,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -333,16 +305,6 @@ ...@@ -333,16 +305,6 @@
" [ 3.14159265, 1.39460474, -0.44313783, 1.09757975, -1.09757975,\n", " [ 3.14159265, 1.39460474, -0.44313783, 1.09757975, -1.09757975,\n",
" 0.44313783, -1.39460474, 3.14159265])\n" " 0.44313783, -1.39460474, 3.14159265])\n"
] ]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\users\\v_zhanglei48\\baidu\\personal-code\\pq\\paddle_quantum\\QSVT\\qsp.py:242: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[i] = np.log(P.coef[n] / Q.coef[m]) * -1j / 2\n",
"c:\\users\\v_zhanglei48\\baidu\\personal-code\\pq\\paddle_quantum\\QSVT\\qsp.py:256: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[0] = -1j * np.log(P.coef[0])\n"
]
} }
], ],
"source": [ "source": [
...@@ -360,16 +322,16 @@ ...@@ -360,16 +322,16 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 23,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"--Rz(-6.28)----Rx(-2.21)----Rz(2.789)----Rx(-2.21)----Rz(-0.88)----Rx(-2.21)----Rz(2.195)----Rx(-2.21)----Rz(-2.19)----Rx(-2.21)----Rz(0.886)----Rx(-2.21)----Rz(-2.78)----Rx(-2.21)----Rz(-6.28)--\n", "--Rz(-6.28)----Rx(-2.73)----Rz(2.789)----Rx(-2.73)----Rz(-0.88)----Rx(-2.73)----Rz(2.195)----Rx(-2.73)----Rz(-2.19)----Rx(-2.73)----Rz(0.886)----Rx(-2.73)----Rz(-2.78)----Rx(-2.73)----Rz(-6.28)--\n",
" \n", " \n",
"模拟 P(x) 的误差是 2.931881168672019e-08\n" "模拟 P(x) 的误差是 1.7893723625681406e-08\n"
] ]
} }
], ],
...@@ -415,7 +377,7 @@ ...@@ -415,7 +377,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 24,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -603,13 +565,6 @@ ...@@ -603,13 +565,6 @@
"换句话说,当 $W = V$ 时,QSVT 将一个 $A$ 的块编码转为了一个 $P(A)$ 的块编码。" "换句话说,当 $W = V$ 时,QSVT 将一个 $A$ 的块编码转为了一个 $P(A)$ 的块编码。"
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
...@@ -619,7 +574,7 @@ ...@@ -619,7 +574,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 25,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -628,18 +583,9 @@ ...@@ -628,18 +583,9 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 26,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [],
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\v_zhanglei48\\Anaconda3\\envs\\personal-code\\lib\\site-packages\\paddle\\fluid\\dygraph\\math_op_patch.py:276: UserWarning: The dtype of left and right variables are not the same, left dtype is paddle.float64, but right dtype is paddle.float32, the right dtype will convert to paddle.float64\n",
" warnings.warn(\n"
]
}
],
"source": [ "source": [
"# 找到 P(A) 及\n", "# 找到 P(A) 及\n",
"# find P(A) and its expected eigenvalues, note that they are computed in different ways\n", "# find P(A) and its expected eigenvalues, note that they are computed in different ways\n",
...@@ -654,7 +600,7 @@ ...@@ -654,7 +600,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": 27,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -662,8 +608,8 @@ ...@@ -662,8 +608,8 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"模拟 P(X) 的误差\n", "模拟 P(X) 的误差\n",
" 最大绝对值, 8.429041888826793e-08\n", " 最大绝对值, 1.450928305889582e-07\n",
" 百分比, 6.291585659021523e-07\n" " 百分比, 3.370838085602552e-07\n"
] ]
} }
], ],
...@@ -675,7 +621,7 @@ ...@@ -675,7 +621,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 12, "execution_count": 28,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -683,8 +629,8 @@ ...@@ -683,8 +629,8 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"模拟 P(X) 的本征值误差\n", "模拟 P(X) 的本征值误差\n",
" 最大绝对值, 1.0894675602300946e-07\n", " 最大绝对值, 1.8375562631798232e-07\n",
" 百分比, 5.209560480945378e-07\n" " 百分比, 2.776073800450616e-07\n"
] ]
} }
], ],
...@@ -714,13 +660,6 @@ ...@@ -714,13 +660,6 @@
"![U_Phi](figures/QSVT-fig-U_Phi.png \"图 2: QSVT 的量子实现,这里 k 是 P 的多项式次数\")" "![U_Phi](figures/QSVT-fig-U_Phi.png \"图 2: QSVT 的量子实现,这里 k 是 P 的多项式次数\")"
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
...@@ -730,7 +669,7 @@ ...@@ -730,7 +669,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 13, "execution_count": 29,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -745,14 +684,14 @@ ...@@ -745,14 +684,14 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 14, "execution_count": 30,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"期望量子态和实际量子态的差距是 2.253552555988302e-07\n" "期望量子态和实际量子态的差距是 3.0184617834444857e-07\n"
] ]
} }
], ],
...@@ -802,13 +741,6 @@ ...@@ -802,13 +741,6 @@
"我们可得,对于多项式 $P$ 的 $\\mathcal{X}$ 的量子奇异值变换是 $B := \\frac{1}{\\sin(\\frac{\\pi}{2k})} A$ 的块编码。" "我们可得,对于多项式 $P$ 的 $\\mathcal{X}$ 的量子奇异值变换是 $B := \\frac{1}{\\sin(\\frac{\\pi}{2k})} A$ 的块编码。"
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
...@@ -818,7 +750,7 @@ ...@@ -818,7 +750,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 15, "execution_count": 31,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -832,14 +764,14 @@ ...@@ -832,14 +764,14 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 16, "execution_count": 32,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"QSVT 的精确值为 3.3605192584218457e-07\n" "QSVT 的精确值为 3.1799856969882967e-07\n"
] ]
} }
], ],
...@@ -893,7 +825,7 @@ ...@@ -893,7 +825,7 @@
"orig_nbformat": 4, "orig_nbformat": 4,
"vscode": { "vscode": {
"interpreter": { "interpreter": {
"hash": "1e82098cfee7be27b5e385e3f85fe91d734d6114f7d09dccafdaad2c23171c3e" "hash": "08942b1340a5932ff3a93f52933a99b0e263568f3aace1d262ffa4d9a0f2da31"
} }
} }
}, },
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 17,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -121,7 +121,7 @@ ...@@ -121,7 +121,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 18,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -155,23 +155,16 @@ ...@@ -155,23 +155,16 @@
"The success probability of decoding is the probability of measuring $0^{\\otimes (m - n)}$, which can be exponentially small as $m - n$ increases. However, this would no longer be a problem if we can control the size of first register. In this tutorial we will assume $m = n + 1$." "The success probability of decoding is the probability of measuring $0^{\\otimes (m - n)}$, which can be exponentially small as $m - n$ increases. However, this would no longer be a problem if we can control the size of first register. In this tutorial we will assume $m = n + 1$."
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Realization in PaddleQuantum"
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 19,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"qubits [0] collapse to the state |0> with probability 0.10231761176954456\n" "qubits [0] collapse to the state |0> with probability 0.25072506070137024\n"
] ]
} }
], ],
...@@ -185,11 +178,11 @@ ...@@ -185,11 +178,11 @@
"input_state = paddle_quantum.State(paddle.kron(zero_state @ dagger(zero_state), rho))\n", "input_state = paddle_quantum.State(paddle.kron(zero_state @ dagger(zero_state), rho))\n",
"\n", "\n",
"# define auxiliary register\n", "# define auxiliary register\n",
"aux_register = [i for i in range(num_qubits - num_block_qubits)]\n", "aux_register = list(range(num_qubits - num_block_qubits))\n",
"\n", "\n",
"# construct the circuit\n", "# construct the circuit\n",
"cir = Circuit(num_qubits)\n", "cir = Circuit(num_qubits)\n",
"cir.oracle(U, [i for i in range(num_qubits)])\n", "cir.oracle(U, list(range(num_qubits)))\n",
"cir.collapse(aux_register, desired_result='0', if_print = True) # call Collapse operator\n", "cir.collapse(aux_register, desired_result='0', if_print = True) # call Collapse operator\n",
"\n", "\n",
"# get output_state and actual output rho\n", "# get output_state and actual output rho\n",
...@@ -206,14 +199,14 @@ ...@@ -206,14 +199,14 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 20,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"the difference between input and output is 1.1947226861933412e-07\n" "the difference between input and output is 0.0\n"
] ]
} }
], ],
...@@ -223,13 +216,6 @@ ...@@ -223,13 +216,6 @@
"print(f\"the difference between input and output is {paddle.norm(paddle.abs(expect_rho - output_rho)).item()}\")" "print(f\"the difference between input and output is {paddle.norm(paddle.abs(expect_rho - output_rho)).item()}\")"
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Special Case ($m = 1$)"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
...@@ -277,7 +263,7 @@ ...@@ -277,7 +263,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 21,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -300,13 +286,6 @@ ...@@ -300,13 +286,6 @@
"Note that such structure of $W_\\Phi$ naturally fits the family of Chebyshev polynomials. Indeed, for a Chebyshev polynomials of first kind with order $k$, its corresponding $\\Phi$ is $(0, \\pi, ..., \\pi)$ (if $k$ is even) or $(\\pi, ..., \\pi)$ (if $k$ is odd). " "Note that such structure of $W_\\Phi$ naturally fits the family of Chebyshev polynomials. Indeed, for a Chebyshev polynomials of first kind with order $k$, its corresponding $\\Phi$ is $(0, \\pi, ..., \\pi)$ (if $k$ is even) or $(\\pi, ..., \\pi)$ (if $k$ is odd). "
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Realization in PaddleQuantum"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
...@@ -316,7 +295,7 @@ ...@@ -316,7 +295,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 22,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -327,16 +306,6 @@ ...@@ -327,16 +306,6 @@
" [ 3.14159265, 1.39460474, -0.44313783, 1.09757975, -1.09757975,\n", " [ 3.14159265, 1.39460474, -0.44313783, 1.09757975, -1.09757975,\n",
" 0.44313783, -1.39460474, 3.14159265])\n" " 0.44313783, -1.39460474, 3.14159265])\n"
] ]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\users\\v_zhanglei48\\baidu\\personal-code\\pq\\paddle_quantum\\QSVT\\qsp.py:242: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[i] = np.log(P.coef[n] / Q.coef[m]) * -1j / 2\n",
"c:\\users\\v_zhanglei48\\baidu\\personal-code\\pq\\paddle_quantum\\QSVT\\qsp.py:256: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[0] = -1j * np.log(P.coef[0])\n"
]
} }
], ],
"source": [ "source": [
...@@ -354,16 +323,16 @@ ...@@ -354,16 +323,16 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 23,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"--Rz(-6.28)----Rx(-1.11)----Rz(2.789)----Rx(-1.11)----Rz(-0.88)----Rx(-1.11)----Rz(2.195)----Rx(-1.11)----Rz(-2.19)----Rx(-1.11)----Rz(0.886)----Rx(-1.11)----Rz(-2.78)----Rx(-1.11)----Rz(-6.28)--\n", "--Rz(-6.28)----Rx(-1.74)----Rz(2.789)----Rx(-1.74)----Rz(-0.88)----Rx(-1.74)----Rz(2.195)----Rx(-1.74)----Rz(-2.19)----Rx(-1.74)----Rz(0.886)----Rx(-1.74)----Rz(-2.78)----Rx(-1.74)----Rz(-6.28)--\n",
" \n", " \n",
"the error of simulating P(x) is 2.1200956324443587e-07\n" "the error of simulating P(x) is 6.378721218542117e-08\n"
] ]
} }
], ],
...@@ -409,7 +378,7 @@ ...@@ -409,7 +378,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 24,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -596,13 +565,6 @@ ...@@ -596,13 +565,6 @@
"That is, when $W = V$, QSVT maps a block encoding of $A$ to a block encoding of $P(A)$." "That is, when $W = V$, QSVT maps a block encoding of $A$ to a block encoding of $P(A)$."
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Realization in PaddleQuantum"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
...@@ -612,38 +574,18 @@ ...@@ -612,38 +574,18 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 25,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [],
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\v_zhanglei48\\Desktop\\QSVT 教程\\qsp.py:242: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[i] = np.log(P.coef[n] / Q.coef[m]) * -1j / 2\n",
"c:\\Users\\v_zhanglei48\\Desktop\\QSVT 教程\\qsp.py:256: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[0] = -1j * np.log(P.coef[0])\n"
]
}
],
"source": [ "source": [
"qsvt = QSVT(P, U, num_block_qubits)" "qsvt = QSVT(P, U, num_block_qubits)"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 26,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [],
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\v_zhanglei48\\Anaconda3\\envs\\personal-code\\lib\\site-packages\\paddle\\fluid\\dygraph\\math_op_patch.py:276: UserWarning: The dtype of left and right variables are not the same, left dtype is paddle.float64, but right dtype is paddle.float32, the right dtype will convert to paddle.float64\n",
" warnings.warn(\n"
]
}
],
"source": [ "source": [
"# find P(A) and its expected eigenvalues, note that they are computed in different ways\n", "# find P(A) and its expected eigenvalues, note that they are computed in different ways\n",
"expect_PX = poly_matrix(P, A).numpy()\n", "expect_PX = poly_matrix(P, A).numpy()\n",
...@@ -657,7 +599,7 @@ ...@@ -657,7 +599,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": 27,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -665,8 +607,8 @@ ...@@ -665,8 +607,8 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"error of simulating P(X)\n", "error of simulating P(X)\n",
" maximum absolute, 1.1376122748011288e-07\n", " maximum absolute, 1.6997650495437753e-07\n",
" percentage, 6.770728859410114e-07\n" " percentage, 3.4201093195057237e-07\n"
] ]
} }
], ],
...@@ -678,7 +620,7 @@ ...@@ -678,7 +620,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 12, "execution_count": 28,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -686,8 +628,8 @@ ...@@ -686,8 +628,8 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"error of eigenvalues of simulating P(X)\n", "error of eigenvalues of simulating P(X)\n",
" maximum absolute, 1.113571654238586e-07\n", " maximum absolute, 2.3308557146996258e-07\n",
" percentage, 6.064903308479878e-07\n" " percentage, 2.2962108806419593e-07\n"
] ]
} }
], ],
...@@ -717,13 +659,6 @@ ...@@ -717,13 +659,6 @@
"![U_Phi](figures/QSVT-fig-U_Phi.png \"Figure 2: Quantum Circuit for QSVT, where k is the degree of P\")" "![U_Phi](figures/QSVT-fig-U_Phi.png \"Figure 2: Quantum Circuit for QSVT, where k is the degree of P\")"
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Realization in PaddleQuantum"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
...@@ -733,7 +668,7 @@ ...@@ -733,7 +668,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 13, "execution_count": 29,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -748,14 +683,14 @@ ...@@ -748,14 +683,14 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 14, "execution_count": 30,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"the different between expected and actual state is 1.6115462390639646e-07\n" "the different between expected and actual state is 2.383485634049416e-07\n"
] ]
} }
], ],
...@@ -805,13 +740,6 @@ ...@@ -805,13 +740,6 @@
"implies that the QSVT of $\\mathcal{X}$ in terms of polynomial $P$ is a block encoding of $B := \\frac{1}{\\sin(\\frac{\\pi}{2k})} A$, as required." "implies that the QSVT of $\\mathcal{X}$ in terms of polynomial $P$ is a block encoding of $B := \\frac{1}{\\sin(\\frac{\\pi}{2k})} A$, as required."
] ]
}, },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Realization in PaddleQuantum"
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
...@@ -821,7 +749,7 @@ ...@@ -821,7 +749,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 15, "execution_count": 31,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -835,14 +763,14 @@ ...@@ -835,14 +763,14 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 16, "execution_count": 32,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"the accuracy of quantum singular value transformation is 2.805196857025294e-07\n" "the accuracy of quantum singular value transformation is 3.029211939065135e-07\n"
] ]
} }
], ],
...@@ -896,7 +824,7 @@ ...@@ -896,7 +824,7 @@
"orig_nbformat": 4, "orig_nbformat": 4,
"vscode": { "vscode": {
"interpreter": { "interpreter": {
"hash": "1e82098cfee7be27b5e385e3f85fe91d734d6114f7d09dccafdaad2c23171c3e" "hash": "08942b1340a5932ff3a93f52933a99b0e263568f3aace1d262ffa4d9a0f2da31"
} }
} }
}, },
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 变分量子精密测量"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<em> Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 概览"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"量子精密测量通过利用量子理论(如量子纠缠)来研究如何高精度和高灵敏度测量物理参数,其理论方面为量子参数估计理论,应用方面为量子传感。已有研究表明,利用量子理论估计的参数精度相较于经典方法有开根号的提升 [1-3]。对于一个未知参数,量子精密测量的目标就是最大化参数估计精度的同时最小化所需要的资源(如时间、量子比特数等)。\n",
"\n",
"例如,给定一个酉算子 $e^{-i\\phi H}$,其中 $H$ 是给定的哈密顿量,那么如何用量子精密测量的方法估计未知参数 $\\phi$ 呢?主要包括以下四步:\n",
"1. 初始化:制备系统初始输入态 $|\\psi\\rangle$;\n",
"2. 参数化:输入态在 $e^{-i\\phi H}$ 的演化下得到参数化的量子态 $e^{-i\\phi H}|\\psi\\rangle$;\n",
"3. 测量:测量输入态经过含有未知参数演化后得到的输出态;\n",
"4. 经典估计:根据多次测量的结果估计未知参数的值。\n",
"\n",
"结合量子参数估计理论的知识,本教程根据 [4] 中变分量子传感的思想,利用量桨构建变分量子电路训练损失函数,得到能够估计服从正态分布的参数的传感器,即优化后的量子电路。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 参数估计理论"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 经典参数估计"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"经典参数估计,如最大似然估计(maximum-likelihood estimator, MLE),通过利用多个样本来计算包含未知参数信息的数据,即给定一个含有未知参数 $\\phi$ 的概率分布,$f$ 为概率密度函数,${\\bf X}=\\{X_1,X_2,...,X_N\\}$ 为 $N$ 个独立同分布的样本数据,利用 MLE 得到 $\\phi$ 的估计值 $\\hat\\phi$:\n",
"\n",
"$$\n",
"\\hat\\phi({\\bf X})=\\arg\\max_{\\phi}\\prod_i^Nf(X_i,\\phi), \\tag{1}\n",
"$$\n",
"\n",
"即求一个 $\\phi$ 使得 $X_i(i=1,...,N)$ 同时出现的概率最大。在得到估计的参数后,如何衡量估计得到的参数与实际参数的差距呢?即如何衡量估计参数的精度?参数估计理论中一般利用均方误差(mean squared error, MSE)来衡量这一精度。\n",
"\n",
"含有未知参数 $\\bf{\\phi}$ 的样本为 ${\\bf{X}}=\\{X_1,X_2,...,X_N\\}\\in \\mathcal F^N$,${\\mathcal F}^N$ 为样本空间,假设 $\\hat{\\phi}({\\bf X}):{\\mathcal F}^N\\rightarrow\\Phi$ 为估计 $\\phi\\in\\Phi$ 的估计器($\\Phi$ 为参数空间),则 $\\hat{\\phi}({\\bf X})$ 关于 $\\phi$ 的均方误差定义为\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"\\rm{MSE}&=E[(\\hat{\\phi}({\\bf X})-\\phi)^2]\\\\\n",
"&=\\sum_{{\\bf X}\\in {\\mathcal F}^N}f({\\bf X};\\phi)(\\hat{\\phi}({\\bf X})-\\phi)^2, \\tag{2}\n",
"\\end{aligned}\n",
"$$\n",
"\n",
"其中 $f({\\bf X};\\phi)$ 为给定参数后的得到当前样本 $\\bf X$ 的概率密度。尽管不同的估计器会影响 MSE 的值,但不论 $\\hat{\\phi}({\\bf X})$ 的选择如何,都有 ${\\rm{MSE}}\\geq\\frac{1}{N{\\mathcal I}(\\phi)}$,该式被称为 Cramér–Rao (CR) 界 [2],其中 ${\\mathcal I}(\\phi)$ 为费舍信息,用于描述参数变化对分布影响的物理量(关于费舍信息在量桨上的应用可参见教程[量子费舍信息](https://qml.baidu.com/tutorials/qnn-research/quantum-fisher-information.html))。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 量子参数估计"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"在量子参数估计中,经典参数估计中利用的一组样本变成了待测的量子态 $\\rho_{\\phi}\\in{\\mathcal F}({\\mathcal H})$,其中 $\\phi$ 为未知参数,${\\mathcal F}({\\mathcal H})$ 表示希尔伯特空间 $\\mathcal H$ 上的密度算子集。根据量子精密测量的四步及文献 [4],假设初始输入态为 $|0\\rangle$,估计器为 $\\hat{\\phi}(m)$($m$ 与测量结果有关,例如为测量得到的比特串中 1 和 0 的个数差),其关于待估计参数 $\\phi$ 的均方误差为\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"{\\rm MSE}&=E[(\\hat{\\phi}(m)-\\phi)^2]\\\\\n",
"&=\\sum_m(\\hat{\\phi}(m)-\\phi)^2p(m|\\phi),\n",
"\\end{aligned} \\tag{3}\n",
"$$\n",
"\n",
"其中 $p(m|\\phi)$ 为给定参数 $\\phi$ 时测量得到 $m$ 的概率。同样的,不论 $\\hat{\\phi}(m)$ 如何选择,有量子 CR 界 ${\\rm{MSE}}\\geq\\frac{1}{NF(\\phi)}$,其中 $N$ 为重复测量 $\\rho_\\phi$ 的次数,$F(\\phi)$ 为量子费舍信息(关于量子费舍信息在量桨上的应用可参见教程[量子费舍信息](https://qml.baidu.com/tutorials/qnn-research/quantum-fisher-information.html))。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 变分量子传感"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"量子传感属于量子精密测量的应用,本教程依据文献 [4],主要介绍一类变分拉姆塞干涉仪。传统的拉姆塞干涉仪 [2](如图 1)是一种利用磁共振现象测量粒子跃迁频率的粒子干涉测量法,被用于测量未知参数;变分拉姆塞干涉仪 [6](如图 2)是在电路编码和解码部分设置两个参数化量子电路(记为 $U_{\\rm{En}}(\\theta_{\\rm En}), U_{\\rm{De}}(\\theta_{\\rm{De}})$),用于设置纠缠和测量量子态。已有研究表明,结合量子理论(如量子纠缠),估计参数的精度由 $O(\\frac{1}{\\sqrt N})$ 提高到 $O(\\frac{1}{N})$ [1-3],而通过变分的方式可以有效地找到合适的纠缠方式。\n",
"\n",
"![Ramsey_interferomertry](./figures/QM-fig-RI.png \"图1:拉姆塞干涉仪。\")\n",
"\n",
"图 1 所示为拉姆塞干涉仪估计参数的过程 [2]。左图是拉姆塞干涉仪的经典设定方式,右图为使用了量子纠缠的拉姆塞干涉仪。相比于经典设定,使用量子纠缠后的干涉仪在估计未知参数的精度上有开根号的优势。\n",
"\n",
"![V_Ramsey_interferomertry](./figures/QM-fig-V_RI3.png \"图2:变分拉姆塞干涉仪。\")\n",
"\n",
"图 2 所示为变分拉姆塞干涉仪。其中两个参数化量子电路通过优化找到电路有效的纠缠方式。\n",
"\n",
"下面我们利用量桨搭建参数化量子电路,来研究利用变分拉姆塞干涉仪估计的服从正态分布的参数的精度。主要分为以下三步:\n",
"1. 初始化;\n",
"2. 评估;\n",
"3. 优化。\n",
"\n",
"参数服从的正态分布的概率密度函数为:\n",
"\n",
"$$\n",
"f(x)=\\frac{1}{\\sqrt{2\\pi}\\nu}\\exp(-\\frac{(x-\\mu)^2}{2\\nu^2}), \\tag{4}\n",
"$$\n",
"\n",
"$\\mu$ 为均值,$\\nu^2$ 为方差。此时变分拉姆塞干涉仪的损失函数为:\n",
"\n",
"$$\n",
"C(\\theta_{\\rm En},\\theta_{\\rm De},a)=\\int d\\phi f(\\phi){\\rm{MSE}}(\\phi), \\tag{5}\n",
"$$\n",
"\n",
"其中选择的估计器为 $\\hat\\phi(m)=am$,$a$ 为需要优化的参数,$m$ 为测量得到的比特串中 1 和 0 的个数差,MSE 为 ${\\rm MSE}(\\phi)=\\sum_m(\\hat{\\phi}(m)-\\phi)^2p_{\\theta}(m|\\phi)$,$\\theta=(\\theta_{\\rm En},\\theta_{\\rm De})$。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1. 初始化"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- 设置 $|0\\rangle$ 为初始输入态;\n",
"- 设置编码和解码部分的参数化量子电路 $U_{\\rm{En}},U_{\\rm{De}}$;\n",
"- 将未知参数编码至电路中。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们在量桨中实现上述过程,首先引入需要的包:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:07:36.541872Z",
"start_time": "2022-11-04T07:07:32.740301Z"
}
},
"outputs": [],
"source": [
"import numpy as np\n",
"from math import exp, pi\n",
"import matplotlib.pyplot as plt\n",
"from typing import Optional, Tuple, List\n",
"\n",
"import paddle\n",
"import paddle_quantum as pq\n",
"from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.loss import Measure\n",
"\n",
"pq.set_backend('state_vector')\n",
"pq.set_dtype('complex128')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"接下来我们构建 $N$ 量子比特的参数化量子电路完成初始化操作。\n",
"<!-- 其中未知参数服从正态分布,其概率密度函数为:\n",
"$$\n",
"f(x)=\\frac{1}{\\sqrt{(2\\pi)}\\sigma}exp(-\\frac{(x-\\mu)^2}{2\\sigma^2}),\n",
"$$\n",
"本教程中,我们选择 $\\mu=0,\\sigma=1$。 -->\n",
"整个电路包括以下五部分:\n",
"- $R_y^{\\otimes N}(\\frac{\\pi}{2})$ 门;\n",
"- 编码电路 $U_{\\rm{En}}(\\theta_{\\rm{En}})$;\n",
"- 含有未知参数的 $R_z^{\\otimes N}(\\phi)=e^{-i\\phi J_z}$,其中 $J_z=\\frac{1}{2}\\sum_{k=1}^N\\sigma_z^{(k)}$, $\\sigma_z^{(k)}$ 表示作用在第 $k$ 个量子比特上的泡利 $Z$ 算符;\n",
"- 解码电路 $U_{\\rm{De}}(\\theta_{\\rm{De}})$;\n",
"- $R_x^{\\otimes N}(\\frac{\\pi}{2})$ 门。\n",
"\n",
"因此整个电路的酉算子可以由\n",
"$$\n",
"U(\\phi,\\theta_{\\rm{En}},\\theta_{\\rm{De}})=R_x^{\\otimes N}(\\frac{\\pi}{2})U_{\\rm{De}}(\\theta_{\\rm{De}})R_z^{\\otimes N}(\\phi)U_{\\rm{En}}(\\theta_{\\rm{En}})R_y^{\\otimes N}(\\frac{\\pi}{2}) \\tag{6}\n",
"$$\n",
"来表示,当编码电路和解码电路的电路层数为 $0$ 时,此时的电路即为经典设定下的拉姆塞干涉仪。下面我们搭建编码和解码电路。"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:07:36.557814Z",
"start_time": "2022-11-04T07:07:36.544862Z"
}
},
"outputs": [],
"source": [
"def RamseyCircuit(theta_EN: paddle.Tensor, theta_DE: paddle.Tensor, input_phi: float) -> Circuit:\n",
" r\"\"\" 构建拉姆塞干涉仪的变分电路\n",
" \n",
" Args:\n",
" theta_EN: 编码层参数, shape 为 [编码层深度,比特数,3]\n",
" theta_DE: 解码层参数, shape 为 [解码层深度,比特数,3]\n",
" input_phi: 未知参数输入\n",
" \n",
" Returns:\n",
" 创建完成的参数化量子电路\n",
" \n",
" \"\"\"\n",
" depth_EN, depth_DE = theta_EN.shape[0], theta_DE.shape[0]\n",
" num_qubits = theta_EN.shape[1]\n",
" \n",
" cir = Circuit(num_qubits)\n",
" cir.ry(param=pi/2)\n",
" \n",
" # 搭建编码电路,生成存在纠缠的输入态\n",
" for depth in range(depth_EN):\n",
" cir.u3(param=theta_EN[depth])\n",
" cir.cnot()\n",
" \n",
" # 未知参数门\n",
" cir.rz(param=input_phi)\n",
" \n",
" # 搭建解码电路,旋转测量基\n",
" for depth in range(depth_DE):\n",
" cir.cnot()\n",
" cir.u3(param=theta_DE[depth])\n",
" \n",
" cir.rx(param=pi/2)\n",
" return cir"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 2. 评估"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- 计算测量结果 $m$ 的概率分布;\n",
"- 计算估计器 $\\hat{\\phi}(m)=am$ 的 MSE,${\\rm MSE}=\\sum_m(\\hat{\\phi}(m)-\\phi)^2p_{\\theta}(m|\\phi)$;\n",
"- 计算损失函数,此处我们利用离散形式的损失函数来近似其积分形式:\n",
"\n",
"$$\n",
"C=\\sum_{k=1}^t\\frac{2}{t}f(\\phi_k){\\rm {MSE}}, \\tag{7}\n",
"$$\n",
"\n",
"其中 $t$ 为选取区间内划分的份数(本教程中区间大小为 2),$f(\\phi_k)$ 为 $\\phi_k$ 对应的概率。"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:07:36.572763Z",
"start_time": "2022-11-04T07:07:36.559813Z"
}
},
"outputs": [],
"source": [
"# 定义计算 m 的函数用于计算测量结果中 1 和 0 的个数差\n",
"def calculate_m(num_qubits: int)-> List[int]:\n",
" m_list = []\n",
" for k in range(2**num_qubits):\n",
" k_bin = list(bin(k)[2:].zfill(num_qubits))\n",
" u = k_bin.count('1')\n",
" v = k_bin.count('0')\n",
" m = u - v\n",
" m_list.append(m)\n",
"\n",
" return m_list\n",
"\n",
"\n",
"def MSE(qnn: paddle.nn.Layer, phi: float) -> paddle.Tensor:\n",
" r\"\"\" 计算对应 phi 的 MSE\n",
" \n",
" Args:\n",
" cir: 拉姆塞干涉仪的变分电路\n",
" phi: 采样参数\n",
" a: 估计器的优化参数\n",
" \n",
" Returns:\n",
" phi 的均方误差\n",
" \n",
" \"\"\"\n",
" cir = RamseyCircuit(qnn.theta_EN, qnn.theta_DE, phi)\n",
" \n",
" # 测量\n",
" output_state = cir()\n",
" prob = Measure()(output_state)\n",
" \n",
" num_qubits = cir.num_qubits\n",
" m = calculate_m(num_qubits)\n",
" return sum([((phi - qnn.a * m[i]) ** 2) * prob[i] for i in range(2 ** num_qubits)])\n",
"\n",
"# 定义损失函数\n",
"def loss_func(qnn: paddle.nn.Layer, sampling_times: int, mean: float, variance: float):\n",
" r\"\"\" 计算 QNN 模型的损失函数\n",
" \n",
" Args:\n",
" qnn: 一个 QNN 模型\n",
" sampling_times: 划分区间的个数\n",
" mean: 未知参数服从的正态分布的均值\n",
" variance: 未知参数服从的正态分布的方差\n",
" \n",
" \"\"\"\n",
" list_phi = [] # phi 的采样列表\n",
" list_pdf = [] # phi 的概率密度函数样本\n",
" for i in range(sampling_times):\n",
" phi = mean - 1 + (2 * i + 1)/ sampling_times # phi 的范围 [u - 1, u + 1],每个 phi 取小区间端点处两个 phi 的均值 \n",
" prob = (1 / (((2 * pi) ** 0.5) * variance)) * exp(-((phi - mean) ** 2) / (2 * (variance**2))) # phi 的概率密度函数\n",
" list_phi.append(phi)\n",
" list_pdf.append(prob)\n",
" \n",
" return sum([list_pdf[i] * MSE(qnn, list_phi[i]) * (2 / sampling_times) for i in range(sampling_times)])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 3. 优化"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:07:36.588710Z",
"start_time": "2022-11-04T07:07:36.576756Z"
}
},
"outputs": [],
"source": [
"def optimization(qnn: paddle.nn.Layer, num_itr: int, learning_rate: float) -> None:\n",
" r\"\"\" 对 QNN 模型进行优化\n",
" \n",
" Args:\n",
" qnn: 一个 QNN 模型\n",
" num_itr: 迭代次数\n",
" learning_rate: 学习速率\n",
" \n",
" \"\"\"\n",
" opt = paddle.optimizer.Adam(learning_rate=learning_rate, parameters=qnn.parameters())\n",
" print(\"训练开始:\")\n",
" for itr in range(1, num_itr):\n",
" loss = qnn()\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
"\n",
" if itr % 10 == 0:\n",
" print(\" iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:07:36.620687Z",
"start_time": "2022-11-04T07:07:36.608645Z"
}
},
"outputs": [],
"source": [
"class RamseyInterferometer(paddle.nn.Layer):\n",
" r\"\"\" 变分拉姆塞干涉仪\n",
" \n",
" \"\"\"\n",
" def __init__(self) -> None:\n",
" super().__init__()\n",
" \n",
" # 为干涉仪添加参数\n",
" theta_EN = self.create_parameter(\n",
" shape= [depth_EN, N, 3], dtype=\"float64\",\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * pi),\n",
" )\n",
" theta_DE = self.create_parameter(\n",
" shape= [depth_DE, N, 3], dtype=\"float64\",\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * pi),\n",
" )\n",
" self.add_parameter('theta_EN', theta_EN)\n",
" self.add_parameter('theta_DE', theta_DE)\n",
" \n",
" # 添加对估计器的优化参数\n",
" a = self.create_parameter(\n",
" shape= [1], dtype=\"float64\",\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * pi),\n",
" )\n",
" self.add_parameter('a', a)\n",
" \n",
" def forward(self) -> paddle.Tensor:\n",
" r\"\"\" 计算损失函数\n",
" \n",
" \"\"\"\n",
" return loss_func(self, TIMES, MEAN, VAR)\n",
" \n",
" def opt(self) -> None:\n",
" r\"\"\" 优化 QNN 模型\n",
" \n",
" \"\"\"\n",
" optimization(self, num_itr=ITR, learning_rate=LR)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"N = 2 # 量子比特数\n",
"depth_EN = 3 # 编码电路的层数\n",
"depth_DE = 3 # 解码电路的层数\n",
"LR = 0.2 # 基于梯度下降的优化方法的学习率\n",
"ITR = 150 # 训练的总迭代次数\n",
"TIMES = 30 # 划分参数区间的个数\n",
"MEAN = 2 # 未知参数服从正态分布的均值\n",
"VAR = 1 # 未知参数服从正态分布的方差"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:33:36.330577Z",
"start_time": "2022-11-04T07:29:01.098077Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"训练开始:\n",
" iter: 10 loss: 5.6798\n",
" iter: 20 loss: 2.3396\n",
" iter: 30 loss: 1.1354\n",
" iter: 40 loss: 0.8429\n",
" iter: 50 loss: 0.4878\n",
" iter: 60 loss: 0.3399\n",
" iter: 70 loss: 0.2571\n",
" iter: 80 loss: 0.2270\n",
" iter: 90 loss: 0.2140\n",
" iter: 100 loss: 0.2067\n",
" iter: 110 loss: 0.2031\n",
" iter: 120 loss: 0.2013\n",
" iter: 130 loss: 0.2001\n",
" iter: 140 loss: 0.1994\n"
]
}
],
"source": [
"QNN = RamseyInterferometer()\n",
"QNN.opt()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"利用优化后的电路估计服从正态分布的参数 $\\phi$,绘制 MSE。"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:33:38.108968Z",
"start_time": "2022-11-04T07:33:36.333569Z"
},
"scrolled": true
},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 600x400 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"phi_list = []\n",
"mse_list = []\n",
"for i in range(TIMES):\n",
" phi = MEAN - 1 + (2 * i + 1) / TIMES\n",
" mse_est = MSE(QNN, phi)\n",
" phi_list.append(phi)\n",
" mse_list.append(mse_est)\n",
"\n",
"font = {'family': 'Times New Roman', 'weight':'normal', 'size':16}\n",
"plt.figure(dpi=100)\n",
"plt.plot(phi_list,mse_list,color='darkblue', linestyle='-')\n",
"plt.scatter(phi_list,mse_list)\n",
"plt.xlabel('$\\\\phi$',font)\n",
"plt.ylabel('MSE',font)\n",
"\n",
"plt.grid()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 结论"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"从图中可以看到,优化后的量子传感器(即量子电路)估计服从正态分布的参数的 MSE 与参数的概率密度呈负相关(MSE 与 $\\phi$ 的图像呈下凸型),且 MSE 在正态分布的均值(即 $\\mu$ 的取值)附近最低,说明在均值附近传感器估计参数的精度高,进一步说明通过变分的方式得到的传感器对该服从正态分布的参数有效。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## 参考文献"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[1] Braunstein S L, Caves C M. Statistical distance and the geometry of quantum states[J]. [Physical Review Letters, 1994, 72(22): 3439](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.72.3439).\n",
"\n",
"[2] Giovannetti V, Lloyd S, Maccone L. Quantum metrology[J]. [Physical review letters, 2006, 96(1): 010401](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.96.010401).\n",
"\n",
"[3] Tóth G, Apellaniz I. Quantum metrology from a quantum information science perspective[J]. [Journal of Physics A: Mathematical and Theoretical, 2014, 47(42): 424006](https://iopscience.iop.org/article/10.1088/1751-8113/47/42/424006/meta).\n",
"\n",
"[4] Marciniak C D, Feldker T, Pogorelov I, et al. Optimal metrology with programmable quantum sensors[J]. [Nature, 2022, 603(7902): 604-609](https://www.nature.com/articles/s41586-022-04435-4).\n",
"\n",
"[5] Giovannetti V, Lloyd S, Maccone L. Advances in quantum metrology[J]. [Nature photonics, 2011, 5(4): 222-229](https://www.nature.com/articles/nphoton.2011.35).\n",
"\n",
"[6] Kaubruegger R, Vasilyev D V, Schulte M, et al. Quantum variational optimization of Ramsey interferometry and atomic clocks[J]. [Physical Review X, 2021, 11(4): 041045](https://journals.aps.org/prx/abstract/10.1103/PhysRevX.11.041045)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.7.13 ('py3.7_pq2.2.1')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.13"
},
"vscode": {
"interpreter": {
"hash": "4e4e2eb86ad73936e915e7c7629a18a8ca06348106cf3e66676b9578cb1a47dd"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Variational Quantum Metrology"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<em> Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Background"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Quantum metrology is the study of high resolution and high sensitivity measurements of physical parameters using quantum theory (e.g., quantum entanglement) in order to further describe physical systems. Its theoretical aspect is quantum parameter estimation theory, and its experimental aspect is called quantum sensing. Previous studies have shown that the estimation accuracy by quantum theory is improved by taking the square root of the required number of samples compared with the classical method [1-3]. For an unknown parameter, the goal of quantum metrology is to maximize the accuracy of parameter estimation while minimizing the required resources (such as time, number of qubits, etc.).\n",
"\n",
"For example, given a unitary $e^{-i\\phi H}$ with a known Hamiltonian $H$, how can we extract the information about $\\phi$ by quantum metrology? It comprises the following four steps:\n",
"\n",
"1. Preparation. Prepare an input state $|\\psi\\rangle$.\n",
"2. Parameterization. Obtain the parameterized quantum state $e^{-i\\phi H}|\\psi\\rangle$ after the evolution of $e^{-i\\phi H}$.\n",
"3. Measurement. Measure the output state.\n",
"4. Classical estimation. Estimate the parameters based on the results of multiple measurements.\n",
"\n",
"Combined with quantum parameter estimation theory, this tutorial is based on the idea of variational quantum sensors in [4], and the parameterized quantum circuit is constructed using Paddle Quantum to train the loss function and obtain a quantum sensor that can estimate the parameters that are normally distributed."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Parameter Estimation Theory"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Classical parameter estimation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Classical parameter estimation, such as the maximum-likelihood estimator (MLE), uses multiple samples to calculate data containing an unknown parameter. That is, given a probability distribution depending on an unknown parameter $\\phi$, where $f$ is the probability density function, and ${\\bf{X}}=\\{X_1,X_2,... X_N\\}$ is $N$ independent and identically distributed sample data, the estimated value $\\hat\\phi$ is obtained by using MLE:\n",
"\n",
"$$\n",
"\\hat\\phi({\\bf X})=\\arg\\max_{\\phi}\\prod_i^Nf(X_i,\\phi), \\tag{1}\n",
"$$\n",
"\n",
"which finds $\\phi$ such that $X_i(i=1,...,N)$ occurs with maximum probability. After obtaining the estimated parameters, how to measure the difference between the estimated parameters and the actual parameters? That is, how to measure the accuracy of the estimated parameters? In the parameter estimation theory, mean squared error (MSE) is generally used to measure the accuracy.\n",
"\n",
"The sample containing the unknown parameter $\\bf{\\phi}$ is ${\\bf{X}}=\\{X_1,X_2,... X_N\\}\\in {\\mathcal F}^N$, where ${\\mathcal F}^N$ is the sample space. Let $\\hat{\\phi}({\\bf X}):{\\mathcal F}^N\\rightarrow\\Phi$ be the estimator that estimates $\\phi\\in\\Phi$ ($\\Phi$ is the parameter space). Then the MSE of $\\hat{\\phi}(\\bf X)$ with respect to $\\phi$ is defined as\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"{\\rm{MSE}}&=\n",
"E[(\\hat{\\phi}({\\bf X})-\\phi)^2]\\\\\n",
"&=\\sum_{{\\bf X}\\in {\\mathcal F}^N}f({\\bf X};\\phi)(\\hat{\\phi}({\\bf X)}-\\phi)^2,\n",
"\\end{aligned} \\tag{2}\n",
"$$\n",
"\n",
"where $f({\\bf X}; \\phi)$ is the probability density of getting the current sample $\\bf X$ given the parameter $\\phi$. Although different estimators affect the value of MSE, regardless of the choice of $\\hat{\\phi}({\\bf X})$, there is ${\\rm{MSE}}\\geq\\frac{1}{N{\\mathcal I(\\phi)}}$. This lower bound is known as the Cramér–Rao (CR) bound [2], where $\\mathcal I(\\phi)$ is the Fisher information, which reflects to what extent a slight parameter change will change the probability distribution (see [Quantum Fisher Information](https://qml.baidu.com/tutorials/qnn-research/quantum-fisher-information.html) for the application of Fisher information in Paddle Quantum). "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Quantum parameter estimation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In quantum parameter estimation, the samples used in classical parameter estimation become a quantum state $\\rho_{\\phi}\\in{\\mathcal F}({\\mathcal H})$ to be measured, where $\\phi$ is the unknown parameter and ${\\mathcal F}({\\mathcal H})$ denotes the set of density operators on the Hilbert space $\\mathcal H$. According to the steps of quantum metrology and [4], assuming that the initial input state is $|0\\rangle$ and the estimator is $\\hat{\\phi}(m)$, where $m$ is related to the measured results (such as the difference in the number of 1s and the number of 0s in the measured bit string), then the MSE is\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"{\\rm MSE}&=E[(\\hat{\\phi}(m)-\\phi)^2]\\\\\n",
"&=\\sum_m(\\hat{\\phi}(m)-\\phi)^2p(m|\\phi),\n",
"\\end{aligned} \\tag{3}\n",
"$$\n",
"\n",
"where $p(m|\\phi)$ is the probability of obtaining $m$ by measurement with a given parameter $\\phi$. Similarly, no matter how $\\hat{\\phi}(m)$ is chosen, there is a quantum CR bound ${\\rm{MSE}}\\geq\\frac{1}{NF(\\phi)}$, where $N$ is the number of repeated measurements of $\\rho_\\phi$, and $F(\\phi)$ is the quantum Fisher information (see [Quantum Fisher Information](https://qml.baidu.com/tutorials/qnn-research/quantum-fisher-information.html) for the application of quantum Fisher information in Paddle Quantum). "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Variational Quantum Sensors"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Quantum sensing is the application of quantum metrology. Based on [4], this tutorial mainly introduces a kind of variational Ramsey interferometer. The traditional Ramsey interferometer [2] (as shown in Figure 1) is a particle interferometry method that uses magnetic resonance phenomena to measure particle transition frequency and is used to measure unknown parameters. The variational Ramsay interferometer [6] (as shown in Figure 2) consists of two parameterized quantum circuits (denoted as $U_{\\rm{En}}(\\theta_{\\rm En})$ and $U_{\\rm{De}}(\\theta_{\\rm{De}})$) in the encoding and decoding parts of the circuit for setting entanglement and measuring quantum states. Previous studies have shown that by combining with quantum theory (such as quantum entanglement), the scaling of parameter estimation accuracy is enhanced to $\\frac{1}{N}$ from $\\frac{1}{\\sqrt{N}}$ [1-3], and the appropriate entanglement mode can be effectively found by the variational method.\n",
"\n",
"![Ramsey_interferomertry](./figures/QM-fig-RI.png \"Figure 1:Ramsey interferometer.\")\n",
"<center> Figure 1:Ramsey interferometer. On the left is the classical configuration of a Ramsey interferometer, and on the right is a Ramsey interferometer using quantum entanglement. Compared with the classical setting, the interferometer using quantum entanglement has an advantage in the order of square root in the accuracy of estimating unknown parameters. </center>\n",
"\n",
"![V_Ramsey_interferomertry](./figures/QM-fig-V_RI3.png \"Figure 2:Variational Ramsey interferometer.\")\n",
"<center> Figure 2:Variational Ramsey interferometer. Two parameterized quantum circuits are optimized to find an efficient way of utilizing entanglement. </center>\n",
"\n",
"In the following, we construct a parameterized quantum circuit using Paddle Quantum to investigate the accuracy of estimating normally distributed parameters by a variational Ramsey interferometer. There are three steps as follows.\n",
"1. Initialization.\n",
"2. Evaluation.\n",
"3. Optimization.\n",
"\n",
"The probability density function of the parameters that obey the normal distribution is:\n",
"\n",
"$$\n",
"f(x)=\\frac{1}{\\sqrt{2\\pi}\\nu}\\exp(-\\frac{(x-\\mu)^2}{2\\nu^2}), \\tag{4}\n",
"$$\n",
"\n",
"where $\\mu$ is the mean and $\\nu^2$ is the variance. In this case, the loss function of the variational Ramsey interferometer is:\n",
"\n",
"$$\n",
"C(\\theta_{\\rm En},\\theta_{\\rm De},a)=\\int d\\phi f(\\phi){\\rm{MSE}}(\\phi), \\tag{5}\n",
"$$\n",
"\n",
"where the estimator is $\\hat\\phi(m)=am$, $a$ is a parameter to be optimized, $m$ is the difference in the number of 1s and the number of 0s in the measured bit string, ${\\rm MSE(\\phi)}=\\sum_m(\\hat{\\phi}(m)-\\phi)^2p_{\\theta}(m|\\phi)$, and $\\theta=(\\theta_{\\rm En},\\theta_{\\rm De})$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1. Initializaiton"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- Set the input state as $|0\\rangle$.\n",
"- Construct the parameterized quantum circuits $U_{\\rm{En}}$ and $U_{\\rm{De}}$ for encoding and decoding, respectively.\n",
"- Encode the unknown parameter."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let us import the necessary packages:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:28:26.890500Z",
"start_time": "2022-11-04T07:28:23.298471Z"
}
},
"outputs": [],
"source": [
"import numpy as np\n",
"from math import exp, pi\n",
"import matplotlib.pyplot as plt\n",
"from typing import Optional, Tuple, List\n",
"\n",
"import paddle\n",
"import paddle_quantum as pq\n",
"from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.loss import Measure\n",
"\n",
"pq.set_backend('state_vector')\n",
"pq.set_dtype('complex128')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we construct a parameterized quantum circuit of $N$ qubits to complete the initialization.\n",
"\n",
"The whole circuit includes the following five parts.\n",
"- $R_y^{\\otimes N}(\\frac{\\pi}{2})$ gates.\n",
"- Encoding circuit $U_{\\rm{En}}(\\theta_{\\rm{En}})$.\n",
"- Circuit with unknown parameter $R_z^{\\otimes N}(\\phi)=e^{-i\\phi J_z}$, where $J_z=\\frac{1}{2}\\sum_{k=1}^N\\sigma_z^{(k)}$.\n",
"- Decoding circuit $U_{\\rm{De}}(\\theta_{\\rm{De}})$.\n",
"- $R_x^{\\otimes N}(\\frac{\\pi}{2})$ gate.\n",
"\n",
"So the unitary of the entire circuit is\n",
"\n",
"$$\n",
"U(\\phi,\\theta_{\\rm{En}},\\theta_{\\rm{De}})=R_x^{\\otimes N}(\\frac{\\pi}{2})U_{\\rm{De}}(\\theta_{\\rm{De}})R_z^{\\otimes N}(\\phi)U_{\\rm{En}}(\\theta_{\\rm{En}})R_y^{\\otimes N}(\\frac{\\pi}{2}). \\tag{6}\n",
"$$\n",
"\n",
"When the depth of the encoding circuit and decoding circuit is $0$, the entire circuit is the classical configuration of a Ramsey interferometer. Here we construct the encoding and the decoding circuits."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:28:26.906450Z",
"start_time": "2022-11-04T07:28:26.892489Z"
}
},
"outputs": [],
"source": [
"def RamseyCircuit(theta_EN: paddle.Tensor, theta_DE: paddle.Tensor, input_phi: float) -> Circuit:\n",
" r\"\"\" Construct variational Ramsey interferometer\n",
" \n",
" Args:\n",
" theta_EN: the parameters of encoding circuit, shape is [depth_En, num_qubits,3]\n",
" theta_DE: the parameters of decoding circui, shape is [depth_De, num_qubits,3]\n",
" input_phi: unknown parameter\n",
" \n",
" Returns:\n",
" Circuit\n",
" \n",
" \"\"\"\n",
" depth_EN, depth_DE = theta_EN.shape[0], theta_DE.shape[0]\n",
" num_qubits = theta_EN.shape[1]\n",
" \n",
" cir = Circuit(num_qubits)\n",
" cir.ry(param=pi/2)\n",
" \n",
" # Construct the encoding circuit to generate an entangled state\n",
" for depth in range(depth_EN):\n",
" cir.u3(param=theta_EN[depth])\n",
" cir.cnot()\n",
" \n",
" # the gate of unknown parameter\n",
" cir.rz(param=input_phi)\n",
" \n",
" # Construct the decoding circuit to rotate the measurement basis\n",
" for depth in range(depth_DE):\n",
" cir.cnot()\n",
" cir.u3(param=theta_DE[depth])\n",
" \n",
" cir.rx(param=pi/2)\n",
" \n",
" return cir"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 2. Evaluation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- Calculate the probability distribution of $m$.\n",
"- Calculate the MSE of the estimator $\\hat{\\phi}(m)=am$: ${\\rm MSE}=\\sum_m(\\hat{\\phi}(m)-\\phi)^2p_{\\theta}(m|\\phi)$.\n",
"- Calculate the loss function. Here, we use the discrete form of the loss function to approximate its integral form:\n",
"\n",
"$$\n",
"C=\\sum_{k=1}^t\\frac{2}{t}f(\\phi_k){\\rm {MSE}}, \\tag{7}\n",
"$$\n",
"\n",
"where $t$ is the number of partitions in the selected interval (the total interval size is $2$ in this tutorial), and $f(\\phi_k)$ is the probability corresponding to $\\phi_k$."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:28:26.922400Z",
"start_time": "2022-11-04T07:28:26.908437Z"
}
},
"outputs": [],
"source": [
"# Define the function to calculate m\n",
"def calculate_m(num_qubits: int)-> List[int]:\n",
" m_list = []\n",
" for k in range(2**num_qubits):\n",
" k_bin = list(bin(k)[2:].zfill(num_qubits))\n",
" u = k_bin.count('1')\n",
" v = k_bin.count('0')\n",
" m = u - v\n",
" m_list.append(m)\n",
"\n",
" return m_list\n",
"\n",
"\n",
"def MSE(qnn: paddle.nn.Layer, phi: float) -> paddle.Tensor:\n",
" r\"\"\" Calculate MSE \n",
" \n",
" Args:\n",
" cir: variational Ramsey interferometer\n",
" phi: unknown parameter\n",
" a: parameter of the estimator\n",
" \n",
" Returns:\n",
" MSE\n",
" \n",
" \"\"\"\n",
" cir = RamseyCircuit(qnn.theta_EN, qnn.theta_DE, phi)\n",
" \n",
" # Measurement\n",
" output_state = cir()\n",
" prob = Measure()(output_state)\n",
" \n",
" num_qubits = cir.num_qubits\n",
" m = calculate_m(num_qubits)\n",
" return sum([((phi - qnn.a * m[i]) ** 2) * prob[i] for i in range(2 ** num_qubits)])\n",
"\n",
"# Define loss function\n",
"def loss_func(qnn: paddle.nn.Layer, sampling_times: int, mean: float, variance: float):\n",
" r\"\"\" Calculate loss \n",
" \n",
" Args:\n",
" qnn: a QNN\n",
" sampling_times: the number of partitions in the selected interval\n",
" mean: the mean of a normal distribution\n",
" variance: the variance of a normal distribution\n",
" \n",
" \"\"\"\n",
" list_phi = [] # The list of phi\n",
" list_pdf = [] # The list of the probability density function of phi\n",
" for i in range(sampling_times):\n",
" phi = mean - 1 + (2 * i + 1)/ sampling_times # The range of phi is [u - 1, u + 1] \n",
" prob = (1 / (((2 * pi) ** 0.5) * variance)) * exp(-((phi - mean) ** 2) / (2 * (variance**2))) # The probability density of phi\n",
" list_phi.append(phi)\n",
" list_pdf.append(prob)\n",
" \n",
" return sum([list_pdf[i] * MSE(qnn, list_phi[i]) * (2 / sampling_times) for i in range(sampling_times)])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 3. Optimization"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:28:26.938337Z",
"start_time": "2022-11-04T07:28:26.926376Z"
}
},
"outputs": [],
"source": [
"def optimization(qnn: paddle.nn.Layer, num_itr: int, learning_rate: float) -> None:\n",
" r\"\"\" Optimize QNN\n",
" \n",
" Args:\n",
" qnn: a QNN\n",
" num_itr: the number of optimization iterations\n",
" learning_rate: learning rate\n",
" \n",
" \"\"\"\n",
" opt = paddle.optimizer.Adam(learning_rate=learning_rate, parameters=qnn.parameters())\n",
" print(\"Begin:\")\n",
" for itr in range(1, num_itr):\n",
" loss = qnn()\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
"\n",
" if itr % 10 == 0:\n",
" print(\" iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:29:44.003762Z",
"start_time": "2022-11-04T07:29:43.988814Z"
}
},
"outputs": [],
"source": [
"class RamseyInterferometer(paddle.nn.Layer):\n",
" r\"\"\" Variational Ramsey interferometer\n",
" \n",
" \"\"\"\n",
" def __init__(self) -> None:\n",
" super().__init__()\n",
" \n",
" # Add parameters\n",
" theta_EN = self.create_parameter(\n",
" shape= [depth_EN, N, 3], dtype=\"float64\",\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * pi),\n",
" )\n",
" theta_DE = self.create_parameter(\n",
" shape= [depth_DE, N, 3], dtype=\"float64\",\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * pi),\n",
" )\n",
" self.add_parameter('theta_EN', theta_EN)\n",
" self.add_parameter('theta_DE', theta_DE)\n",
" \n",
" # Add the parameter of the estimator\n",
" a = self.create_parameter(\n",
" shape= [1], dtype=\"float64\",\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * pi),\n",
" )\n",
" self.add_parameter('a', a)\n",
" \n",
" def forward(self) -> paddle.Tensor:\n",
" r\"\"\" Calculate loss\n",
" \n",
" \"\"\"\n",
" return loss_func(self, TIMES, MEAN, VAR)\n",
" \n",
" def opt(self) -> None:\n",
" r\"\"\" Optimize QNN\n",
" \n",
" \"\"\"\n",
" optimization(self, num_itr=ITR, learning_rate=LR)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"N = 2 # The number of qubits\n",
"depth_EN = 3 # The depth of encoding circuit\n",
"depth_DE = 3 # The depth of decoding circuit\n",
"LR = 0.2 # Learning rate\n",
"ITR = 150 # The number of optimization iterations\n",
"TIMES = 30 # The number of partitions in the selected interval\n",
"MEAN = 2 # The mean of a normal distribution\n",
"VAR = 1 # The variance of a normal distribution"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:44:05.439096Z",
"start_time": "2022-11-04T07:40:20.208851Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Begin:\n",
" iter: 10 loss: 3.8495\n",
" iter: 20 loss: 0.8521\n",
" iter: 30 loss: 0.7484\n",
" iter: 40 loss: 0.4504\n",
" iter: 50 loss: 0.3610\n",
" iter: 60 loss: 0.3375\n",
" iter: 70 loss: 0.3042\n",
" iter: 80 loss: 0.2827\n",
" iter: 90 loss: 0.2600\n",
" iter: 100 loss: 0.2386\n",
" iter: 110 loss: 0.2217\n",
" iter: 120 loss: 0.2096\n",
" iter: 130 loss: 0.2026\n",
" iter: 140 loss: 0.1996\n"
]
}
],
"source": [
"QNN = RamseyInterferometer()\n",
"QNN.opt()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The optimized circuit is used to estimate the parameters with a normal distribution, and then we draw the MSE."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"ExecuteTime": {
"end_time": "2022-11-04T07:44:49.222284Z",
"start_time": "2022-11-04T07:44:47.797824Z"
},
"scrolled": true
},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 600x400 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"phi_list = []\n",
"mse_list = []\n",
"for i in range(TIMES):\n",
" phi = MEAN - 1 + (2 * i + 1) / TIMES\n",
" mse_est = MSE(QNN, phi)\n",
" phi_list.append(phi)\n",
" mse_list.append(mse_est)\n",
"\n",
"font = {'family': 'Times New Roman', 'weight':'normal', 'size':16}\n",
"plt.figure(dpi=100)\n",
"plt.plot(phi_list,mse_list,color='darkblue', linestyle='-')\n",
"plt.scatter(phi_list,mse_list)\n",
"plt.xlabel('$\\\\phi$',font)\n",
"plt.ylabel('MSE',font)\n",
"\n",
"plt.grid()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusion"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see that the MSE estimated by the optimized quantum sensor is negatively correlated with the probability density of the parameters, and the value of MSE is the lowest near the mean value of the normal distribution, indicating that the obtained quantum sensor by variational method is effective for the parameters following a normal distribution."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## References"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[1] Braunstein S L, Caves C M. Statistical distance and the geometry of quantum states[J]. [Physical Review Letters, 1994, 72(22): 3439](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.72.3439).\n",
"\n",
"[2] Giovannetti V, Lloyd S, Maccone L. Quantum metrology[J]. [Physical review letters, 2006, 96(1): 010401](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.96.010401).\n",
"\n",
"[3] Tóth G, Apellaniz I. Quantum metrology from a quantum information science perspective[J]. [Journal of Physics A: Mathematical and Theoretical, 2014, 47(42): 424006](https://iopscience.iop.org/article/10.1088/1751-8113/47/42/424006/meta).\n",
"\n",
"[4] Marciniak C D, Feldker T, Pogorelov I, et al. Optimal metrology with programmable quantum sensors[J]. [Nature, 2022, 603(7902): 604-609](https://www.nature.com/articles/s41586-022-04435-4).\n",
"\n",
"[5] Giovannetti V, Lloyd S, Maccone L. Advances in quantum metrology[J]. [Nature photonics, 2011, 5(4): 222-229](https://www.nature.com/articles/nphoton.2011.35).\n",
"\n",
"[6] Kaubruegger R, Vasilyev D V, Schulte M, et al. Quantum variational optimization of Ramsey interferometry and atomic clocks[J]. [Physical Review X, 2021, 11(4): 041045](https://journals.aps.org/prx/abstract/10.1103/PhysRevX.11.041045)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.13 ('pq')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.13"
},
"vscode": {
"interpreter": {
"hash": "08942b1340a5932ff3a93f52933a99b0e263568f3aace1d262ffa4d9a0f2da31"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册