From a075fc8396c9893eed40151c923b114cab5a1835 Mon Sep 17 00:00:00 2001
From: OriginQuantumCloud
Date: Wed, 28 Jan 2026 16:21:32 +0800
Subject: [PATCH 01/11] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3ed6232..153fe91 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -159,7 +159,7 @@ git merge master
### 测试
-在pull request您的代码之前,请针对您修改的代码编写单元测试,并需要通过现有的测试。alg的测试基于pytest,[pytest使用文档])包含如何使用pytest编写单元测试。
+在pull request您的代码之前,请针对您修改的代码编写单元测试,并需要通过现有的测试。
在编写单元测试之前,您需要先注意一些规范:
@@ -189,3 +189,4 @@ git merge master
+
From e5423a9d7440d3502465d4245a8e1b0b768d452b Mon Sep 17 00:00:00 2001
From: OriginQuantumCloud
Date: Wed, 28 Jan 2026 18:13:31 +0800
Subject: [PATCH 02/11] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 153fe91..d3dfedd 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -125,36 +125,36 @@ alg社区基于本源量子提供的pyqpanda-algorithm开源量子算法仓库
如果你觉得已准备好`code review`,且预备提交PR请求,请确保:
-- 代码遵循项目的代码风格,并且成功通过CI测试。为了方便起见,你可以先在本地执行,提前进行检查并发现报告问题。`
+- 代码遵循项目的代码风格,并且成功通过CI测试。为了方便起见,你可以先在本地执行,提前进行检查并发现报告问题。
-- 如果函数或类在PR期间被修改,请相应更新。如果你的拉取请求是添加一个新的类、函数或模块,那面向用户,请确保你也添加了这些内容文档索引。``
+- 如果函数或类在PR期间被修改,请相应更新。如果你的拉取请求是添加一个新的类、函数或模块,那面向用户,请确保你也添加了这些内容文档索引。
- 确保如果你的更改将对其他用户产生影响(新功能, 弃用、移除等),你已经添加了相关说明。
-在创建Pull Request之前,需要先fork alg [repo](https://github.com/OriginQ/pyqpanda-algorithm),然后使用这个fork中分支向官方仓库创建 Pull Request。在创建pull request时应选择推送到alg官方仓库的`master`分支。
+在创建Pull Request之前,需要先fork alg [repo](https://github.com/OriginQ/pyqpanda-algorithm),然后使用这个fork中分支向官方仓库创建 Pull Request。在创建pull request时应选择推送到alg官方仓库的`develop`分支。
fork 和 Pull Request的基本流程如下:
1. Fork alg的仓库 [repo page](https://github.com/OriginQ/pyqpanda-algorithm)。并把你的克隆仓库下载到你的本机。
-2. 从master分支创建一个新的分支:`git checkout master -b new_branch_name`,其中`new_branch_name` 是你的新分支的名称。
+2. 从`develop`分支创建一个新的分支:`git checkout develop -b new_branch_name`,其中`new_branch_name` 是你的新分支的名称。
3. 把你的修改提交到你自己的分支。
-4. 如果你的克隆仓库与alg的官方仓库不同步,你需要先更新你的克隆仓库的master分支,然后再把master分支合并到你自己的分支(在合并的过程中,你可能要修改一些合并冲突):
+4. 如果你的克隆仓库与alg的官方仓库不同步,你需要先更新你的克隆仓库的`develop`分支,然后再把`develop`分支合并到你自己的分支(在合并的过程中,你可能要修改一些合并冲突):
```
-# Update your local master.
+# Update your local develop.
git fetch upstream
-git checkout master
-git merge upstream/master
-# Merge local master into your branch.
+git checkout develop
+git merge upstream/develop
+# Merge local develop into your branch.
git checkout new_branch_name
-git merge master
+git merge develop
```
5. 把你修改的推送到你克隆的仓库。
`git push origin new_branch_name`
-6. 经过以上的操作,你就可以把你工作 Pull Request 给alg的官方仓库了,在pull request需要选择官方仓库的``master`分支。
+6. 经过以上的操作,你就可以把你工作 Pull Request 给alg的官方仓库了,在pull request需要选择官方仓库的`develop`分支。
7. 审查人员将对您的代码进行审核,并可能要求更改,您可以在本地执行这些操作,然后再次执行上述过程。
### 测试
@@ -190,3 +190,4 @@ git merge master
+
From fccdb9885f081fbaff3aae8efbc71f3a68746774 Mon Sep 17 00:00:00 2001
From: OriginQuantumCloud
Date: Wed, 28 Jan 2026 18:19:01 +0800
Subject: [PATCH 03/11] Update CONTRIBUTING_EN.md
---
CONTRIBUTING_EN.md | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/CONTRIBUTING_EN.md b/CONTRIBUTING_EN.md
index e92249d..5015504 100644
--- a/CONTRIBUTING_EN.md
+++ b/CONTRIBUTING_EN.md
@@ -114,28 +114,28 @@ Before submitting a PR, ensure:
#### PR Workflow
-1. Fork the alg repository ([repo page](https://github.com/OriginQ/QPanda-2)) and clone it locally.
+1. Fork the alg repository ([repo page]([https://github.com/OriginQ/QPanda-2](https://github.com/OriginQ/pyqpanda-algorithm))) and clone it locally.
-2. Create a new branch from `master`:
- `git checkout master -b new_branch_name`
+2. Create a new branch from `develop`:
+ `git checkout develop -b new_branch_name`
3. Commit your changes to the new branch.
-4. Sync your forked `master` with the official repository (resolve merge conflicts if needed):
+4. Sync your forked `develop` with the official repository (resolve merge conflicts if needed):
```bash
- # Update local master
+ # Update local develop
git fetch upstream
- git checkout master
- git merge upstream/master
- # Merge master into your branch
+ git checkout develop
+ git merge upstream/develop
+ # Merge develop into your branch
git checkout new_branch_name
- git merge master
+ git merge develop
```
### Testing
-Write unit tests for modified code and pass all existing tests before submitting a PR. alg uses pytest; refer to the [pytest Primer]for guidance.
+Write unit tests for modified code and pass all existing tests before submitting a PR.
#### Testing Guidelines
From 6506f61bb927e315b411da91acb3dee3d5d77322 Mon Sep 17 00:00:00 2001
From: OriginQuantumCloud
Date: Wed, 28 Jan 2026 18:23:38 +0800
Subject: [PATCH 04/11] Update CONTRIBUTING_EN.md
---
CONTRIBUTING_EN.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CONTRIBUTING_EN.md b/CONTRIBUTING_EN.md
index 5015504..420af45 100644
--- a/CONTRIBUTING_EN.md
+++ b/CONTRIBUTING_EN.md
@@ -107,7 +107,7 @@ To claim an issue (e.g., `good first issue`, `enhancement issue`):
Before submitting a PR, ensure:
-- Code complies with the project’s style and passes CI tests (run `tox` locally to check).
+- Code complies with the project’s style and passes CI tests.
- Relevant documentation (including docstrings) is updated.
- Additional tests are added for impactful changes.
- Release notes are added for user-facing changes (mark the PR as a changelog).
From 91d9c1206dbb6259d9f99bfb94ed014fb48fc19f Mon Sep 17 00:00:00 2001
From: OriginQuantumCloud
Date: Thu, 29 Jan 2026 09:50:22 +0800
Subject: [PATCH 05/11] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d3dfedd..1a0069d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -125,7 +125,7 @@ alg社区基于本源量子提供的pyqpanda-algorithm开源量子算法仓库
如果你觉得已准备好`code review`,且预备提交PR请求,请确保:
-- 代码遵循项目的代码风格,并且成功通过CI测试。为了方便起见,你可以先在本地执行,提前进行检查并发现报告问题。
+- 代码遵循项目的代码风格,并且成功通过CI/CD测试。为了方便起见,你可以先在本地执行,提前进行检查并发现报告问题。
- 如果函数或类在PR期间被修改,请相应更新。如果你的拉取请求是添加一个新的类、函数或模块,那面向用户,请确保你也添加了这些内容文档索引。
@@ -191,3 +191,4 @@ git merge develop
+
From 9500cb968d325a6087a3610dd790573f71b3fd7a Mon Sep 17 00:00:00 2001
From: OriginQuantumCloud
Date: Thu, 29 Jan 2026 09:51:03 +0800
Subject: [PATCH 06/11] Update CONTRIBUTING_EN.md
---
CONTRIBUTING_EN.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CONTRIBUTING_EN.md b/CONTRIBUTING_EN.md
index 420af45..a55cb6d 100644
--- a/CONTRIBUTING_EN.md
+++ b/CONTRIBUTING_EN.md
@@ -107,7 +107,7 @@ To claim an issue (e.g., `good first issue`, `enhancement issue`):
Before submitting a PR, ensure:
-- Code complies with the project’s style and passes CI tests.
+- Code complies with the project’s style and passes CI/CD tests.
- Relevant documentation (including docstrings) is updated.
- Additional tests are added for impactful changes.
- Release notes are added for user-facing changes (mark the PR as a changelog).
From 09730d33478c382bfce692c3548570f607c20ba4 Mon Sep 17 00:00:00 2001
From: shenzhi-git
Date: Mon, 2 Feb 2026 16:09:21 +0800
Subject: [PATCH 07/11] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3?=
=?UTF-8?q?=E7=A0=81=E4=B8=AD=E7=9A=84test?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Tutorials/source/GettingStarted.rst | 18 ++----------------
Tutorials/source/conf.py | 2 +-
Tutorials/source/index.rst | 1 -
pyqpanda-algorithm/pyqpanda_alg/QAOA/qaoa.py | 1 -
.../pyqpanda_alg/QSVM/quantum_kernel_svm.py | 1 -
pyqpanda-algorithm/setup.py | 6 ++----
test/QAOA/Test_complete_xy_mixer.py | 17 +----------------
.../Test_default_circuits_linear_w_state.py | 2 +-
...st_default_circuits_prepare_dicke_state.py | 3 +--
test/QAOA/Test_dstate_linear_w_state.py | 2 +-
test/QAOA/Test_dstate_prepare_dicke_state.py | 2 +-
test/QAOA/Test_init_d_state.py | 19 +++++--------------
test/QAOA/Test_qaoa_QAOA_calculate_energy.py | 11 +++--------
test/QAOA/Test_qaoa_p_0.py | 8 --------
test/QAOA/Test_qaoa_p_1.py | 8 --------
test/QAOA/Test_qaoa_parameter_interpolate.py | 12 +++---------
.../Test_qaoa_pauli_z_operator_to_circuit.py | 12 ++----------
test/QAOA/Test_qaoa_problem_to_z_operator.py | 6 +++---
test/QAOA/Test_spsa_minimize.py | 10 +---------
test/QAOA/Test_xy_mixer.py | 17 +----------------
test/QARM/Test_qarm.py | 1 -
test/QPCA/Test_qpca.py | 5 -----
test/QSVM/Test_qsvm.py | 2 --
test/pytest.ini | 1 -
24 files changed, 28 insertions(+), 139 deletions(-)
diff --git a/Tutorials/source/GettingStarted.rst b/Tutorials/source/GettingStarted.rst
index 0a7d0cd..7452a10 100644
--- a/Tutorials/source/GettingStarted.rst
+++ b/Tutorials/source/GettingStarted.rst
@@ -11,7 +11,7 @@ It contains many practical quantum application algorithms. Installation and use
Configuration
>>>>>>>>>>>>>>>>>>>
-pyqpanda_alg uses C++ as the host language, and its environmental requirements for the system are as follows:
+pyqpanda_alg's environmental requirements for the system are as follows:
Windows
---------------------
@@ -19,8 +19,6 @@ Windows
* - software
- version
- * - `Microsoft Visual C++ Redistributable x64`_
- - 2019
* - Python
- >= 3.11 && <= 3.13
@@ -34,16 +32,4 @@ Linux
* - GCC
- >= 7.5
* - Python
- - >= 3.11 && <= 3.13
-
-
-Install
->>>>>>>>>>>>>>>>>
-
-If you have already installed the python environment and the pip tool, enter the following command in the terminal or console:
-
- .. code-block:: python
-
- pip install pyqpanda_alg
-
-.. note:: If you encounter permission problems under linux, you need to add ``sudo``
\ No newline at end of file
+ - >= 3.11 && <= 3.13
\ No newline at end of file
diff --git a/Tutorials/source/conf.py b/Tutorials/source/conf.py
index f5ccd16..3a09a15 100644
--- a/Tutorials/source/conf.py
+++ b/Tutorials/source/conf.py
@@ -19,7 +19,7 @@
# -- Project information -----------------------------------------------------
project = 'pyqpanda-algorithm'
-copyright = '2023, OriginQC'
+copyright = '2026, OriginQC'
author = 'OriginQC'
# The short X.Y version
diff --git a/Tutorials/source/index.rst b/Tutorials/source/index.rst
index 0866854..4c50a5d 100644
--- a/Tutorials/source/index.rst
+++ b/Tutorials/source/index.rst
@@ -30,7 +30,6 @@ Overall, it provides a standardized set of tools for developers, allowing them t
autoapi/pyqpanda_alg/QAOA/index
autoapi/pyqpanda_alg/QARM/index
- autoapi/pyqpanda_alg/QAlgBase/index
autoapi/pyqpanda_alg/QKmeans/index
autoapi/pyqpanda_alg/QPCA/index
autoapi/pyqpanda_alg/QSVM/index
diff --git a/pyqpanda-algorithm/pyqpanda_alg/QAOA/qaoa.py b/pyqpanda-algorithm/pyqpanda_alg/QAOA/qaoa.py
index 688e01e..210f421 100644
--- a/pyqpanda-algorithm/pyqpanda_alg/QAOA/qaoa.py
+++ b/pyqpanda-algorithm/pyqpanda_alg/QAOA/qaoa.py
@@ -482,7 +482,6 @@ def run_qaoa_circuit(self, gammas, betas, shots=-1):
.. code-block:: python
- import pyqpanda as pq
import sympy as sp
from pyqpanda_alg.QAOA.qaoa import *
diff --git a/pyqpanda-algorithm/pyqpanda_alg/QSVM/quantum_kernel_svm.py b/pyqpanda-algorithm/pyqpanda_alg/QSVM/quantum_kernel_svm.py
index 562976b..4b6da4c 100644
--- a/pyqpanda-algorithm/pyqpanda_alg/QSVM/quantum_kernel_svm.py
+++ b/pyqpanda-algorithm/pyqpanda_alg/QSVM/quantum_kernel_svm.py
@@ -209,7 +209,6 @@ def evaluate(self, x_vec: np.ndarray, y_vec: np.ndarray = None) -> np.ndarray:
import os
import numpy as np
- import pyqpanda as pq
from sklearn.svm import SVC
import matplotlib
try:
diff --git a/pyqpanda-algorithm/setup.py b/pyqpanda-algorithm/setup.py
index fd4b50e..f8aca03 100644
--- a/pyqpanda-algorithm/setup.py
+++ b/pyqpanda-algorithm/setup.py
@@ -6,7 +6,6 @@
requirements = open('requirements.txt').readlines()
requirements = [r.strip() for r in requirements]
-
is_win = (platform.system() == 'Windows')
if is_win:
pd_files = ['*.pyd', '*.dll', '*.pyi']
@@ -20,10 +19,9 @@
license = "Apache Licence",
author = "OriginQ",
install_requires=requirements,
- description= "A Quantum Algorithm Development and Runtime Environment Kit, based on pyqpanda.",
+ description= "A Quantum Algorithm Development and Runtime Environment Kit, based on pyqpanda3.",
packages = find_packages(),
- py_modules = ['psi4_wrapper'],
package_data={
'':pd_files
},
@@ -39,4 +37,4 @@
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13"
],
-)
+)
\ No newline at end of file
diff --git a/test/QAOA/Test_complete_xy_mixer.py b/test/QAOA/Test_complete_xy_mixer.py
index 821d618..2983593 100644
--- a/test/QAOA/Test_complete_xy_mixer.py
+++ b/test/QAOA/Test_complete_xy_mixer.py
@@ -3,7 +3,6 @@
# import pytest
# import numpy as np
#
-# # 添加项目路径到系统路径
# sys.path.append((Path.cwd().parent.parent).__str__())
#
# from pyqpanda3.core import QProg, RX, CPUQVM
@@ -11,14 +10,11 @@
#
#
# class TestCompleteXYMixer:
-# """测试 complete_xy_mixer 接口"""
#
# def setup_method(self):
-# """测试前的初始化"""
# self.machine = CPUQVM()
#
# def calculate_hamming_weight_distribution(self, prob_dict, max_weight):
-# """计算汉明权重分布"""
# weight_probs = {}
# for weight in range(max_weight + 1):
# prob = sum(value for key, value in prob_dict.items()
@@ -27,20 +23,16 @@
# return weight_probs
#
# def test_complete_xy_mixer_hamming_weight_preservation(self):
-# """测试XY mixer应该保持汉明权重"""
# n_qubits = 4
# prog = QProg(n_qubits)
# qubits = prog.qubits()
#
-# # 创建初始状态 - 使用随机RX门创建复杂初始状态
# for q in qubits:
# prog << RX(q, np.random.random() * 2 * np.pi)
#
-# # 获取初始状态的概率分布
# self.machine.run(prog, shots=1)
# original_result = self.machine.result().get_prob_dict()
#
-# # 计算初始汉明权重分布
# original_weight_probs = self.calculate_hamming_weight_distribution(original_result, n_qubits)
#
# # 应用 complete_xy_mixer
@@ -48,27 +40,20 @@
# circuit = default_circuits.complete_xy_mixer(qubits, beta)
# prog << circuit
#
-# # 获取应用mixer后的概率分布
# self.machine.run(prog, shots=1)
# final_result = self.machine.result().get_prob_dict()
#
-# # 计算最终汉明权重分布
# final_weight_probs = self.calculate_hamming_weight_distribution(final_result, n_qubits)
-#
-# # 打印详细的权重分布变化
# print("\nHamming weight distribution comparison:")
# for weight in range(n_qubits + 1):
# original_prob = original_weight_probs.get(weight, 0)
# final_prob = final_weight_probs.get(weight, 0)
# print(f"Hamming weight {weight}: {original_prob:.4f} -> {final_prob:.4f}")
#
-# # XY mixer应该保持汉明权重(在统计误差范围内)
-# # 由于有限shots和数值精度,允许一定的误差
-# tolerance = 0.05 # 5% 容忍度
+# tolerance = 0.05
# assert abs(original_prob - final_prob) < tolerance, \
# f"Hamming weight {weight} not preserved within tolerance: {original_prob:.4f} -> {final_prob:.4f}"
#
#
# if __name__ == "__main__":
-# # 运行测试
# pytest.main([__file__, "-v", "-s"])
\ No newline at end of file
diff --git a/test/QAOA/Test_default_circuits_linear_w_state.py b/test/QAOA/Test_default_circuits_linear_w_state.py
index 99b41bb..fd45bfb 100644
--- a/test/QAOA/Test_default_circuits_linear_w_state.py
+++ b/test/QAOA/Test_default_circuits_linear_w_state.py
@@ -63,5 +63,5 @@
#
#
# if __name__ == "__main__":
-# # 运行测试
+# # run test
# pytest.main([__file__, "-v"])
\ No newline at end of file
diff --git a/test/QAOA/Test_default_circuits_prepare_dicke_state.py b/test/QAOA/Test_default_circuits_prepare_dicke_state.py
index 157ebef..0d34f58 100644
--- a/test/QAOA/Test_default_circuits_prepare_dicke_state.py
+++ b/test/QAOA/Test_default_circuits_prepare_dicke_state.py
@@ -3,7 +3,6 @@
# import pytest
# import numpy as np
#
-# # 添加项目路径到系统路径
# sys.path.append((Path.cwd().parent.parent).__str__())
#
# from pyqpanda_alg.QAOA import default_circuits
@@ -45,5 +44,5 @@
#
#
# if __name__ == "__main__":
-# # 运行测试
+# # run test
# pytest.main([__file__, "-v"])
\ No newline at end of file
diff --git a/test/QAOA/Test_dstate_linear_w_state.py b/test/QAOA/Test_dstate_linear_w_state.py
index 5a91289..0a8aad5 100644
--- a/test/QAOA/Test_dstate_linear_w_state.py
+++ b/test/QAOA/Test_dstate_linear_w_state.py
@@ -55,5 +55,5 @@
#
#
# if __name__ == "__main__":
-# # 运行测试
+# # run test
# pytest.main([__file__, "-v"])
\ No newline at end of file
diff --git a/test/QAOA/Test_dstate_prepare_dicke_state.py b/test/QAOA/Test_dstate_prepare_dicke_state.py
index d2125ec..f8d1670 100644
--- a/test/QAOA/Test_dstate_prepare_dicke_state.py
+++ b/test/QAOA/Test_dstate_prepare_dicke_state.py
@@ -44,5 +44,5 @@
#
#
# if __name__ == "__main__":
-# # 直接运行测试
+# # 直接run test
# pytest.main([__file__, "-v"])
\ No newline at end of file
diff --git a/test/QAOA/Test_init_d_state.py b/test/QAOA/Test_init_d_state.py
index 69e7e4e..cda266a 100644
--- a/test/QAOA/Test_init_d_state.py
+++ b/test/QAOA/Test_init_d_state.py
@@ -3,7 +3,6 @@
# import pytest
# import numpy as np
#
-# # 添加项目路径到系统路径
# sys.path.append((Path.cwd().parent.parent).__str__())
#
# from pyqpanda3.core import QProg, CPUQVM
@@ -11,14 +10,11 @@
#
#
# class TestInitDState:
-# """测试 init_d_state 接口"""
#
# def setup_method(self):
-# """测试前的初始化"""
# self.machine = CPUQVM()
#
# def calculate_domain_hamming_weights(self, result_key, domains):
-# """计算测量结果中每个域的汉明权重"""
# domain_weights = []
# for domain in domains:
# weight = sum(1 for idx in domain if result_key[idx] == '1')
@@ -26,24 +22,20 @@
# return domain_weights
#
# def test_init_d_state_integer_domains(self):
-# """测试整数分区方式的Dicke态初始化"""
# n_qubits = 6
-# k = 2 # 每个域的汉明权重
-# domains = 2 # 分成2个域
+# k = 2
+# domains = 2
#
# prog = QProg(n_qubits)
# qubits = prog.qubits()
#
-# # 创建Dicke态初始化电路
# init_circuit_func = default_circuits.init_d_state(domains, k)
# init_circuit = init_circuit_func(qubits)
# prog << init_circuit
#
-# # 运行量子程序
# self.machine.run(prog, shots=1000)
# results = self.machine.result().get_prob_dict(qubits)
#
-# # 计算域的分区(均匀分成k份)
# domain_size = n_qubits // domains
# domain_list = [list(range(i * domain_size, (i + 1) * domain_size)) for i in range(domains)]
#
@@ -51,13 +43,12 @@
# valid_states = 0
#
# for key, prob in results.items():
-# if prob > 0.001: # 忽略概率很小的状态
-# key_reversed = key[::-1] # 反转键以匹配量子比特顺序
+# if prob > 0.001:
+# key_reversed = key[::-1]
# domain_weights = self.calculate_domain_hamming_weights(key_reversed, domain_list)
#
# print(f"State {key_reversed}: domain weights = {domain_weights}, prob = {prob:.4f}")
#
-# # 验证每个域的汉明权重都等于k
# for weight in domain_weights:
# assert weight == k, f"Domain weight should be {k}, but got {weight} for state {key_reversed}"
#
@@ -67,5 +58,5 @@
#
#
# if __name__ == "__main__":
-# # 运行测试
+# # run test
# pytest.main([__file__, "-v", "-s"])
\ No newline at end of file
diff --git a/test/QAOA/Test_qaoa_QAOA_calculate_energy.py b/test/QAOA/Test_qaoa_QAOA_calculate_energy.py
index 7b20a19..ec8dd1e 100644
--- a/test/QAOA/Test_qaoa_QAOA_calculate_energy.py
+++ b/test/QAOA/Test_qaoa_QAOA_calculate_energy.py
@@ -4,7 +4,6 @@
# import sympy as sp
# import numpy as np
#
-# # 添加项目路径
# sys.path.append((Path.cwd().parent.parent).__str__())
#
# from pyqpanda_alg.QAOA.qaoa import QAOA, p_1
@@ -12,18 +11,14 @@
#
#
# class TestCalculateEnergy:
-# """测试calculate_energy接口"""
#
# def test_basic_functionality_with_symbolic_problem(self):
-# """测试基本功能:使用符号问题计算能量"""
-# # 创建测试问题:f = 2*x0*x1 + 3*x2 - 1
+# # f = 2*x0*x1 + 3*x2 - 1
# vars = sp.symbols('x0:3')
# f = 2*vars[0]*vars[1] + 3*vars[2] - 1
#
-# # 初始化QAOA
# qaoa_f = QAOA(f)
#
-# # 测试不同的解
# test_cases = [
# ([1, 0, 0], 2*1*0 + 3*0 - 1), # f(1,0,0) = -1
# ([0, 1, 1], 2*0*1 + 3*1 - 1), # f(0,1,1) = 2
@@ -35,8 +30,8 @@
# for solution, expected_energy in test_cases:
# calculated_energy = qaoa_f.calculate_energy(solution)
# assert abs(calculated_energy - expected_energy) < 1e-10, \
-# f"解 {solution} 的能量计算错误: 期望 {expected_energy}, 得到 {calculated_energy}"
+# f"solve {solution} error : expect {expected_energy}, get {calculated_energy}"
#
# if __name__ == "__main__":
-# # 运行测试
+# # run test
# pytest.main([__file__, "-v"])
\ No newline at end of file
diff --git a/test/QAOA/Test_qaoa_p_0.py b/test/QAOA/Test_qaoa_p_0.py
index ed0695f..3bc7fd8 100644
--- a/test/QAOA/Test_qaoa_p_0.py
+++ b/test/QAOA/Test_qaoa_p_0.py
@@ -2,26 +2,19 @@
# from pathlib import Path
# import pytest
#
-# # 添加项目路径到系统路径
# sys.path.append((Path.cwd().parent.parent).__str__())
#
-# # 导入测试所需的模块
# from pyqpanda_alg.QAOA import qaoa
# from pyqpanda3.hamiltonian import PauliOperator
#
#
# class TestP0Interface:
-# """测试 qaoa.p_0 接口的功能性"""
#
# def test_p0_basic_functionality(self):
-# """测试 p_0 接口的基本功能 - 返回 PauliOperator 对象"""
-# # 测试不同索引的 Pauli 算符生成
# operator_0 = qaoa.p_0(0)
#
-# # 验证返回对象类型
# assert isinstance(operator_0, PauliOperator)
#
-# # 验证算符有字符串表示
# assert str(operator_0) is not None
# assert len(str(operator_0)) > 0
#
@@ -29,5 +22,4 @@
#
#
# if __name__ == "__main__":
-# # 运行测试
# pytest.main([__file__, "-v", "-s"])
\ No newline at end of file
diff --git a/test/QAOA/Test_qaoa_p_1.py b/test/QAOA/Test_qaoa_p_1.py
index 00457f0..ad80dce 100644
--- a/test/QAOA/Test_qaoa_p_1.py
+++ b/test/QAOA/Test_qaoa_p_1.py
@@ -2,30 +2,22 @@
# from pathlib import Path
# import pytest
#
-# # 添加项目路径到系统路径
# sys.path.append((Path.cwd().parent.parent).__str__())
#
-# # 导入测试所需的模块
# from pyqpanda_alg.QAOA import qaoa
# from pyqpanda3.hamiltonian import PauliOperator
#
#
# class TestP1Interface:
-# """测试 qaoa.p_1 接口的功能性"""
#
# def test_p1_basic_functionality(self):
-# """测试 p_1 接口的基本功能"""
-# # 测试不同索引的 Pauli 算符生成
# operator_0 = qaoa.p_1(0)
# operator_1 = qaoa.p_1(1)
# operator_2 = qaoa.p_1(2)
#
-# # 验证返回对象类型
# assert isinstance(operator_0, PauliOperator)
# assert isinstance(operator_1, PauliOperator)
# assert isinstance(operator_2, PauliOperator)
-#
-# # 验证不同索引生成的算符不同
# assert str(operator_0) != str(operator_1)
# assert str(operator_1) != str(operator_2)
#
diff --git a/test/QAOA/Test_qaoa_parameter_interpolate.py b/test/QAOA/Test_qaoa_parameter_interpolate.py
index a94a3a0..4670fc5 100644
--- a/test/QAOA/Test_qaoa_parameter_interpolate.py
+++ b/test/QAOA/Test_qaoa_parameter_interpolate.py
@@ -3,30 +3,24 @@
import pytest
import numpy as np
-# 添加项目路径到系统路径
sys.path.append((Path.cwd().parent.parent).__str__())
-# 导入测试所需的模块
from pyqpanda_alg.QAOA import qaoa
class TestParameterInterpolate:
- """测试 qaoa.parameter_interpolate 接口的功能性"""
def test_basic_functionality(self):
- """测试基本功能 - 2p个参数插值为2(p+1)个参数"""
- # 使用示例中的测试用例
initial_parameter = np.array([0.1, 0.2, 0.2, 0.1])
new_parameter = qaoa.parameter_interpolate(initial_parameter)
- # 验证返回对象类型和形状
assert isinstance(new_parameter, np.ndarray)
assert new_parameter.shape == (6,) # 2*(2+1)=6
- print(f"输入: {initial_parameter}")
- print(f"输出: {new_parameter}")
+ print(f"{initial_parameter}")
+ print(f"{new_parameter}")
if __name__ == "__main__":
- # 运行测试
+ # run test
pytest.main([__file__, "-v", "-s"])
\ No newline at end of file
diff --git a/test/QAOA/Test_qaoa_pauli_z_operator_to_circuit.py b/test/QAOA/Test_qaoa_pauli_z_operator_to_circuit.py
index 5e988bb..fdaab1b 100644
--- a/test/QAOA/Test_qaoa_pauli_z_operator_to_circuit.py
+++ b/test/QAOA/Test_qaoa_pauli_z_operator_to_circuit.py
@@ -4,7 +4,6 @@
# import sympy as sp
# import numpy as np
#
-# # 添加项目路径
# sys.path.append((Path.cwd().parent.parent).__str__())
#
# from pyqpanda_alg.QAOA import qaoa
@@ -12,32 +11,25 @@
#
#
# class TestPauliZOperatorToCircuit:
-# """测试pauli_z_operator_to_circuit接口"""
#
# @classmethod
# def setup_class(cls):
-# """测试类初始化"""
# cls.machine = CPUQVM()
#
# def setup_method(self):
-# """每个测试方法前的设置"""
# self.prog = QProg(3)
# self.qubits = self.prog.qubits()
#
# def test_basic_functionality(self):
-# """测试基本功能:构造exp(-iH γ)的量子线路"""
-# # 创建测试问题:f = 2*x0*x1 + 3*x2 - 1
+# # f = 2*x0*x1 + 3*x2 - 1
# vars = sp.symbols('x0:3')
# f = 2*vars[0]*vars[1] + 3*vars[2] - 1
#
-# # 将问题转换为Pauli Z算子
# operator = qaoa.problem_to_z_operator(f)
#
-# # 构造量子线路
# gamma = 1.0
# circuit, _ = qaoa.pauli_z_operator_to_circuit(operator, self.qubits, gamma)
#
-# # 验证线路不为空
# assert circuit is not None
# originir_str = circuit.originir()
# assert isinstance(originir_str, str)
@@ -45,5 +37,5 @@
#
#
# if __name__ == "__main__":
-# # 运行测试
+# # run test
# pytest.main([__file__, "-v", "-s"])
\ No newline at end of file
diff --git a/test/QAOA/Test_qaoa_problem_to_z_operator.py b/test/QAOA/Test_qaoa_problem_to_z_operator.py
index 2d8aa8d..4fc0fd5 100644
--- a/test/QAOA/Test_qaoa_problem_to_z_operator.py
+++ b/test/QAOA/Test_qaoa_problem_to_z_operator.py
@@ -21,10 +21,10 @@
#
# assert str(hamiltonian) is not None
# assert len(str(hamiltonian)) > 0
-# print(f"原始表达式: {f}")
-# print(f"转换后的哈密顿量: {hamiltonian}")
+# print(f"{f}")
+# print(f"{hamiltonian}")
#
#
# if __name__ == "__main__":
-# # 运行测试
+# # run test
# pytest.main([__file__, "-v", "-s"])
\ No newline at end of file
diff --git a/test/QAOA/Test_spsa_minimize.py b/test/QAOA/Test_spsa_minimize.py
index 819e394..655f036 100644
--- a/test/QAOA/Test_spsa_minimize.py
+++ b/test/QAOA/Test_spsa_minimize.py
@@ -3,17 +3,14 @@
import sys
from pathlib import Path
-# 添加模块路径
sys.path.append((Path.cwd().parent.parent).__str__())
from pyqpanda_alg.QAOA import spsa
class TestSPSAMinimize:
- """spsa_minimize接口测试类"""
@pytest.fixture
def noise_function(self):
- """创建带噪声的测试函数"""
class NoiseF:
def __init__(self):
self.eval_count = 0
@@ -21,7 +18,6 @@ def __init__(self):
def eval_f(self, x):
self.eval_count += 1
- # 添加高斯噪声的二次函数
return np.linalg.norm(x**2 + np.random.normal(0, 0.1, size=len(x)))
def record(self, x):
@@ -31,13 +27,11 @@ def record(self, x):
@pytest.fixture
def simple_function(self):
- """创建简单的测试函数(无噪声)"""
def func(x):
- return np.sum(x**2) # 简单的二次函数
+ return np.sum(x**2)
return func
def test_spsa_basic_functionality(self, noise_function):
- """测试SPSA基本功能"""
x0 = np.array([1.0, 2.0, 3.0, 4.0])
result = spsa.spsa_minimize(
@@ -47,11 +41,9 @@ def test_spsa_basic_functionality(self, noise_function):
maxiter=50
)
- # 验证结果类型和形状
assert isinstance(result, np.ndarray)
assert result.shape == x0.shape
- # 验证回调函数被调用
assert len(noise_function.history) > 0
assert noise_function.eval_count > 0
diff --git a/test/QAOA/Test_xy_mixer.py b/test/QAOA/Test_xy_mixer.py
index 359dfad..bed781f 100644
--- a/test/QAOA/Test_xy_mixer.py
+++ b/test/QAOA/Test_xy_mixer.py
@@ -3,7 +3,6 @@
# import pytest
# import numpy as np
#
-# # 添加项目路径到系统路径
# sys.path.append((Path.cwd().parent.parent).__str__())
#
# from pyqpanda3.core import QProg, QCircuit, RX, CPUQVM
@@ -11,57 +10,45 @@
#
#
# class TestXYMixer:
-# """测试 xy_mixer 接口"""
#
# def setup_method(self):
-# """测试前的初始化"""
# self.machine = CPUQVM()
#
# def calculate_domain_hamming_weights(self, prob_dict, domains):
-# """计算每个域的汉明权重分布"""
# domain_probs = {}
# for result_key, prob_value in prob_dict.items():
# domain_weights = []
# for domain in domains:
-# # 计算该域的汉明权重
# weight = sum(1 for idx in domain if result_key[idx] == '1')
# domain_weights.append(weight)
#
-# # 将权重元组作为键
# weight_key = tuple(domain_weights)
# domain_probs[weight_key] = domain_probs.get(weight_key, 0) + prob_value
#
# return domain_probs
#
# def test_xy_mixer_integer_domains_parity(self):
-# """测试整数分区方式的parity XY mixer"""
# n_qubits = 4
-# k_domains = 2 # 分成2个域
+# k_domains = 2
#
# prog = QProg(n_qubits)
# qubits = prog.qubits()
#
-# # 创建初始状态
# for q in qubits:
# prog << RX(q, np.random.random() * np.pi)
#
-# # 获取初始状态的概率分布
# self.machine.run(prog, shots=1)
# origin_result = self.machine.result().get_prob_dict()
#
-# # 创建XY mixer电路
# circuit = default_circuits.xy_mixer(k_domains, 'PXY')(qubits, np.pi/2)
# prog << circuit
#
-# # 获取最终状态的概率分布
# self.machine.run(prog, shots=1)
# final_result = self.machine.result().get_prob_dict()
#
-# # 计算域的分区(均匀分成k份)
# domain_size = n_qubits // k_domains
# domains = [list(range(i * domain_size, (i + 1) * domain_size)) for i in range(k_domains)]
#
-# # 计算每个域的汉明权重分布
# origin_domain_probs = self.calculate_domain_hamming_weights(origin_result, domains)
# final_domain_probs = self.calculate_domain_hamming_weights(final_result, domains)
#
@@ -71,11 +58,9 @@
# final_prob = final_domain_probs.get(weight_combo, 0)
# print(f"Domain weights {weight_combo}: {origin_prob:.4f} -> {final_prob:.4f}")
#
-# # 每个域的汉明权重应该保持
# tolerance = 0.06
# assert abs(origin_prob - final_prob) < tolerance, \
# f"Domain weights {weight_combo} not preserved: {origin_prob:.4f} -> {final_prob:.4f}"
#
# if __name__ == "__main__":
-# # 运行测试
# pytest.main([__file__, "-v", "-s"])
\ No newline at end of file
diff --git a/test/QARM/Test_qarm.py b/test/QARM/Test_qarm.py
index 7f1a7a0..339b1f5 100644
--- a/test/QARM/Test_qarm.py
+++ b/test/QARM/Test_qarm.py
@@ -26,7 +26,6 @@ def read(file_path):
def test_qarm_normal_case():
- """测试正常情况下的QARM执行"""
data_path = QARM.__path__[0]
data_file = os.path.join(data_path, 'dataset/data2.txt')
trans_data = read(data_file)
diff --git a/test/QPCA/Test_qpca.py b/test/QPCA/Test_qpca.py
index ca2c40d..31a1047 100644
--- a/test/QPCA/Test_qpca.py
+++ b/test/QPCA/Test_qpca.py
@@ -4,23 +4,18 @@
import pytest
import numpy as np
-# 添加项目路径到系统路径
sys.path.append((Path.cwd().parent.parent).__str__())
class TestQPCA:
- """QPCA测试类"""
@pytest.fixture
def sample_data(self):
- """提供标准测试数据"""
return np.array([[-1, 2], [-2, -1], [-1, -2], [1, 3], [2, 1], [3, 2]])
def test_qpca_normal(self, sample_data):
- """测试QPCA正常功能"""
from pyqpanda_alg.QPCA import qpca
- # 执行QPCA
data_q = qpca(sample_data, 1)
assert data_q.shape == (6,1)
diff --git a/test/QSVM/Test_qsvm.py b/test/QSVM/Test_qsvm.py
index d1e3744..6b5aeb4 100644
--- a/test/QSVM/Test_qsvm.py
+++ b/test/QSVM/Test_qsvm.py
@@ -14,7 +14,6 @@
def _read_vqc_qsvm_data(path):
- """读取量子SVM数据"""
train_features = np.loadtxt(os.path.join(path, "dataset/qsvm_train_features.txt"))
test_features = np.loadtxt(os.path.join(path, "dataset/qsvm_test_features.txt"))
train_labels = np.loadtxt(os.path.join(path, "dataset/qsvm_train_labels.txt"))
@@ -24,7 +23,6 @@ def _read_vqc_qsvm_data(path):
def test_data_loading():
- """测试数据加载功能"""
train_features, test_features, train_labels, test_labels, samples = _read_vqc_qsvm_data(data_path)
assert train_features.shape[1] == 2
diff --git a/test/pytest.ini b/test/pytest.ini
index 2887f64..6b75de7 100644
--- a/test/pytest.ini
+++ b/test/pytest.ini
@@ -1,6 +1,5 @@
[pytest]
testpaths =
- QAlgBase
QAOA
QRAM
QPCA
From 4f8ae66b6caa19ded988bdda9efe3260efb3c1f8 Mon Sep 17 00:00:00 2001
From: OriginQuantumCloud
Date: Tue, 3 Feb 2026 10:11:33 +0800
Subject: [PATCH 08/11] Update README.md
---
README.md | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/README.md b/README.md
index a12fbe0..2e76294 100644
--- a/README.md
+++ b/README.md
@@ -126,6 +126,23 @@ pyqpanda_alg采用Python作为主要语言,对系统的环境要求如下:
------
+## 仓库分支说明
+本项目采用「稳定分支 + 开发分支」的协作模式,不同分支承担不同职责,方便你根据需求选择对应分支操作:
+
+### 📌 main(主分支)
+- **核心定位**:main 分支是项目的**稳定发布分支**,存放的是经过充分测试、可直接使用的开源量子算法代码。
+- **使用场景**:如果你想研究、学习量子算法的核心实现,或直接基于成熟代码进行二次开发,可直接拉取/使用 main 分支的内容。
+- **重要说明**:main 分支不接收任何直接提交或 Pull Request(PR),确保核心代码始终稳定、无已知问题。
+
+### 🛠️ develop(开发分支)
+- **核心定位**:develop 分支是项目的**协作开发分支**,用于整合社区贡献、迭代新功能、修复问题。
+- **提交场景**:如果你想参与项目贡献,包括但不限于:提交 Bug 修复代码、完善项目文档、补充量子算法的示例代码、提出/实现新的功能建议、优化现有算法的性能或可读性,请将所有 Pull Request(PR)**统一提交至 develop 分支**。
+- **协作说明**:我们会定期审核 develop 分支的贡献内容,经测试验证后合并至 main 分支,让优质贡献同步到稳定版本中。
+
+感谢你的理解与配合!
+
+------
+
## 开源许可
使用 [Apache License 2.0](https://gitee.com/OriginQ/alg/blob/master/LICENSE),对 公司、团队、个人 等 商用、非商用 都自由免费且非常友好,请放心使用和登记。
@@ -154,3 +171,4 @@ pyqpanda_alg采用Python作为主要语言,对系统的环境要求如下:
+
From 5d7af4cab02763626b22ffa98a033dc796bd3f49 Mon Sep 17 00:00:00 2001
From: OriginQuantumCloud
Date: Tue, 3 Feb 2026 10:13:29 +0800
Subject: [PATCH 09/11] Update README_EN.md
---
README_EN.md | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/README_EN.md b/README_EN.md
index d74c953..1a645c8 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -90,6 +90,21 @@ pyqpanda_alg is primarily developed in Python with the following system requirem
------
+## Branch Explanation
+This project follows a "stable branch + development branch" collaboration model, with different branches serving distinct purposes to help you choose the right one for your needs.
+
+### 📌 main (Main Branch)
+- **Core Purpose**: The `main` branch is the project's **stable release branch**, containing fully tested, production-ready open-source quantum algorithm code.
+- **Usage Scenario**: If you want to study, learn the core implementation of quantum algorithms, or directly build upon mature code for secondary development, you can pull and use the content from the `main` branch.
+- **Important Note**: The `main` branch does **not** accept any direct commits or Pull Requests (PRs), ensuring the core code remains stable and bug-free.
+
+### 🛠️ develop (Development Branch)
+- **Core Purpose**: The `develop` branch is the project's **collaborative development branch**, used for integrating community contributions, iterating new features, and fixing issues.
+- **Submission Scenario**: If you wish to contribute to the project, including but not limited to:Submitting bug fixes、Improving project documentation (e.g., README, code comments)、Adding example code for quantum algorithms、Proposing or implementing new feature suggestions、Optimizing the performance or readability of existing algorithms,please submit all Pull Requests (PRs) **to the `develop` branch only**.
+- **Collaboration Note**: We regularly review contributions on the `develop` branch. After testing and validation, approved changes will be merged into the `main` branch, so high-quality contributions are synced to the stable release.
+
+------
+
## Open Source License
Licensed under [Apache License 2.0](https://gitee.com/OriginQ/alg/blob/master/LICENSE), free and friendly for commercial/non-commercial use by companies, teams, and individuals. Feel free to use and register.
@@ -116,3 +131,4 @@ Thanks to all contributors, testers, and community supporters. Special thanks to
+
From 9146e0249e3271eecd1a75de5f677eb0bf4d18d1 Mon Sep 17 00:00:00 2001
From: jsjjl
Date: Mon, 15 Jun 2026 09:12:26 +0000
Subject: [PATCH 10/11] =?UTF-8?q?feat:=20=E5=8F=82=E4=B8=8E=E9=A1=B9?=
=?UTF-8?q?=E7=9B=AE=E6=B5=81=E7=A8=8B=E8=AF=B4=E6=98=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: traeagent
---
pyqpanda-algorithm/example/VQE/example_vqe.py | 176 +++++++++++
.../pyqpanda_alg/VQE/__init__.py | 16 +
pyqpanda-algorithm/pyqpanda_alg/VQE/ansatz.py | 75 +++++
pyqpanda-algorithm/pyqpanda_alg/VQE/vqe.py | 292 ++++++++++++++++++
pyqpanda-algorithm/pyqpanda_alg/__init__.py | 2 +-
pyqpanda-algorithm/test/VQE/Test_VQE.py | 139 +++++++++
6 files changed, 699 insertions(+), 1 deletion(-)
create mode 100644 pyqpanda-algorithm/example/VQE/example_vqe.py
create mode 100644 pyqpanda-algorithm/pyqpanda_alg/VQE/__init__.py
create mode 100644 pyqpanda-algorithm/pyqpanda_alg/VQE/ansatz.py
create mode 100644 pyqpanda-algorithm/pyqpanda_alg/VQE/vqe.py
create mode 100644 pyqpanda-algorithm/test/VQE/Test_VQE.py
diff --git a/pyqpanda-algorithm/example/VQE/example_vqe.py b/pyqpanda-algorithm/example/VQE/example_vqe.py
new file mode 100644
index 0000000..65b7dcd
--- /dev/null
+++ b/pyqpanda-algorithm/example/VQE/example_vqe.py
@@ -0,0 +1,176 @@
+# 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.
+
+"""
+VQE (Variational Quantum Eigensolver) Example
+
+This example demonstrates how to use the VQE algorithm to find the ground state
+energy of a simple Hamiltonian.
+"""
+
+from pyqpanda_alg import VQE
+from pyqpanda3.hamiltonian import PauliOperator
+import numpy as np
+
+
+def example_simple_hamiltonian():
+ """
+ Example 1: Simple single-qubit Hamiltonian
+
+ Hamiltonian: H = Z
+ Ground state: |1⟩ with energy -1
+ """
+ print("=" * 60)
+ print("Example 1: Simple Single-Qubit Hamiltonian")
+ print("=" * 60)
+
+ # Define Hamiltonian: H = Z
+ hamiltonian = PauliOperator({"Z0": 1.0})
+
+ # Create VQE solver
+ vqe = VQE.VQE(hamiltonian, layers=2, optimizer='COBYLA')
+
+ # Run VQE
+ energy, params = vqe.run(optimizer_options={'maxiter': 100})
+
+ print(f"Hamiltonian: H = Z")
+ print(f"Theoretical ground state energy: -1.0")
+ print(f"VQE found energy: {energy:.6f}")
+ print(f"Error: {abs(energy - (-1.0)):.6f}")
+ print()
+
+
+def example_two_qubit_hamiltonian():
+ """
+ Example 2: Two-qubit Hamiltonian with interaction
+
+ Hamiltonian: H = 0.5*Z0 + 0.5*Z1 + 0.3*X0*X1
+ """
+ print("=" * 60)
+ print("Example 2: Two-Qubit Hamiltonian with Interaction")
+ print("=" * 60)
+
+ # Define Hamiltonian
+ hamiltonian = PauliOperator({
+ "Z0": 0.5,
+ "Z1": 0.5,
+ "X0 X1": 0.3
+ })
+
+ # Create VQE solver with more layers for better accuracy
+ vqe = VQE.VQE(hamiltonian, layers=3, optimizer='COBYLA')
+
+ # Run VQE
+ energy, params = vqe.run(optimizer_options={'maxiter': 200})
+
+ print(f"Hamiltonian: H = 0.5*Z0 + 0.5*Z1 + 0.3*X0*X1")
+ print(f"VQE found energy: {energy:.6f}")
+ print(f"Number of optimization steps: {len(vqe.energy_history)}")
+ print()
+
+
+def example_custom_ansatz():
+ """
+ Example 3: Using custom ansatz circuit
+ """
+ print("=" * 60)
+ print("Example 3: Custom Ansatz Circuit")
+ print("=" * 60)
+
+ from pyqpanda3.core import QCircuit, RY, RZ, CNOT
+
+ # Define custom ansatz
+ def custom_ansatz(qubit_list, params):
+ circuit = QCircuit()
+ n_qubits = len(qubit_list)
+
+ # First layer: RY rotations
+ for i, q in enumerate(qubit_list):
+ circuit << RY(q, params[i])
+
+ # Entangling layer
+ for i in range(n_qubits - 1):
+ circuit << CNOT(qubit_list[i], qubit_list[i + 1])
+
+ # Second layer: RZ rotations
+ for i, q in enumerate(qubit_list):
+ circuit << RZ(q, params[n_qubits + i])
+
+ return circuit
+
+ # Define Hamiltonian
+ hamiltonian = PauliOperator({
+ "Z0": 1.0,
+ "Z1": 1.0,
+ "X0 X1": 0.5
+ })
+
+ # Create VQE with custom ansatz
+ initial_params = np.random.uniform(-np.pi, np.pi, 4) # 2 qubits * 2 params
+ vqe = VQE.VQE(
+ hamiltonian,
+ ansatz=custom_ansatz,
+ initial_params=initial_params,
+ optimizer='COBYLA'
+ )
+
+ # Run VQE
+ energy, params = vqe.run(optimizer_options={'maxiter': 150})
+
+ print(f"Custom ansatz with RY-CNOT-RZ structure")
+ print(f"VQE found energy: {energy:.6f}")
+ print()
+
+
+def example_energy_convergence():
+ """
+ Example 4: Track energy convergence during optimization
+ """
+ print("=" * 60)
+ print("Example 4: Energy Convergence Tracking")
+ print("=" * 60)
+
+ # Define Hamiltonian
+ hamiltonian = PauliOperator({
+ "Z0": 1.0,
+ "X0": 0.5
+ })
+
+ # Create VQE solver
+ vqe = VQE.VQE(hamiltonian, layers=2, optimizer='COBYLA')
+
+ # Run VQE
+ energy, params = vqe.run(optimizer_options={'maxiter': 100})
+
+ print(f"Hamiltonian: H = Z0 + 0.5*X0")
+ print(f"Final energy: {energy:.6f}")
+ print(f"Optimization steps: {len(vqe.energy_history)}")
+ print(f"Initial energy: {vqe.energy_history[0]:.6f}")
+ print(f"Final energy: {vqe.energy_history[-1]:.6f}")
+ print(f"Energy reduction: {vqe.energy_history[0] - vqe.energy_history[-1]:.6f}")
+ print()
+
+
+if __name__ == "__main__":
+ print("\n" + "=" * 60)
+ print("VQE (Variational Quantum Eigensolver) Examples")
+ print("=" * 60 + "\n")
+
+ # Run all examples
+ example_simple_hamiltonian()
+ example_two_qubit_hamiltonian()
+ example_custom_ansatz()
+ example_energy_convergence()
+
+ print("=" * 60)
+ print("All examples completed successfully!")
+ print("=" * 60)
diff --git a/pyqpanda-algorithm/pyqpanda_alg/VQE/__init__.py b/pyqpanda-algorithm/pyqpanda_alg/VQE/__init__.py
new file mode 100644
index 0000000..7b67a9c
--- /dev/null
+++ b/pyqpanda-algorithm/pyqpanda_alg/VQE/__init__.py
@@ -0,0 +1,16 @@
+# 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.
+
+from .vqe import VQE
+from .ansatz import hardware_efficient_circuit
+
+__all__ = ['VQE', 'hardware_efficient_circuit']
diff --git a/pyqpanda-algorithm/pyqpanda_alg/VQE/ansatz.py b/pyqpanda-algorithm/pyqpanda_alg/VQE/ansatz.py
new file mode 100644
index 0000000..9313df1
--- /dev/null
+++ b/pyqpanda-algorithm/pyqpanda_alg/VQE/ansatz.py
@@ -0,0 +1,75 @@
+# 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.
+
+from pyqpanda3.core import CPUQVM, QCircuit, QProg, RY, RZ, CNOT, H
+import numpy as np
+
+
+def hardware_efficient_circuit(qubit_list, params, layers=1):
+ """
+ Construct a hardware-efficient ansatz circuit for VQE.
+
+ This ansatz uses single-qubit rotation gates (RY, RZ) and entangling gates (CNOT)
+ arranged in a pattern that is efficient for near-term quantum hardware.
+
+ Parameters
+ qubit_list : ``list[int]``\n
+ List of qubit indices to apply the ansatz on.
+ params : ``array-like``\n
+ Parameters for the rotation gates. Should have length 2 * len(qubit_list) * layers.
+ For each layer, we need 2 parameters per qubit (one for RY, one for RZ).
+ layers : ``int``\n
+ Number of layers in the ansatz circuit. Default is 1.
+
+ Returns
+ circuit : ``QCircuit``\n
+ The parameterized quantum circuit.
+
+ Examples
+ >>> from pyqpanda_alg.VQE import hardware_efficient_circuit
+ >>> import numpy as np
+ >>> qubits = [0, 1, 2]
+ >>> params = np.random.random(2 * 3 * 2) # 2 qubits * 3 layers * 2 params per qubit
+ >>> circuit = hardware_efficient_circuit(qubits, params, layers=2)
+ >>> print(circuit)
+
+ References
+ [1] Kandala A, Mezzacapo A, Temme K, et al. Hardware-efficient variational quantum
+ eigensolver for small molecules and quantum magnets[J]. Nature, 2017, 549(7671): 242-246.
+ https://doi.org/10.1038/nature23879
+ """
+ n_qubits = len(qubit_list)
+ circuit = QCircuit()
+
+ # Validate params length
+ expected_length = 2 * n_qubits * layers
+ if len(params) != expected_length:
+ raise ValueError(f"Expected {expected_length} parameters, got {len(params)}")
+
+ param_idx = 0
+
+ for layer in range(layers):
+ # Single-qubit rotation layer
+ for i, qubit in enumerate(qubit_list):
+ # RY rotation
+ circuit << RY(qubit, params[param_idx])
+ param_idx += 1
+ # RZ rotation
+ circuit << RZ(qubit, params[param_idx])
+ param_idx += 1
+
+ # Entangling layer (linear connectivity)
+ if layer < layers - 1: # No entangling layer after the last rotation layer
+ for i in range(len(qubit_list) - 1):
+ circuit << CNOT(qubit_list[i], qubit_list[i + 1])
+
+ return circuit
diff --git a/pyqpanda-algorithm/pyqpanda_alg/VQE/vqe.py b/pyqpanda-algorithm/pyqpanda_alg/VQE/vqe.py
new file mode 100644
index 0000000..daa3814
--- /dev/null
+++ b/pyqpanda-algorithm/pyqpanda_alg/VQE/vqe.py
@@ -0,0 +1,292 @@
+# 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.
+
+from pyqpanda3.core import CPUQVM, QCircuit, QProg
+from pyqpanda3.hamiltonian import PauliOperator, Hamiltonian
+import numpy as np
+from scipy.optimize import minimize
+from typing import Union, Callable, Optional
+from .ansatz import hardware_efficient_circuit
+
+
+class VQE:
+ """
+ Variational Quantum Eigensolver (VQE) algorithm for finding the ground state energy
+ of a Hamiltonian.
+
+ VQE is a hybrid quantum-classical algorithm that uses a parameterized quantum circuit
+ (ansatz) to prepare trial states, and a classical optimizer to minimize the energy
+ expectation value. It is particularly suitable for near-term quantum devices.
+
+ Parameters
+ hamiltonian : ``PauliOperator`` or ``Hamiltonian``\n
+ The Hamiltonian whose ground state energy we want to find.
+ ansatz : ``callable``, ``optional``\n
+ A function that constructs the parameterized quantum circuit (ansatz).
+ Should accept (qubit_list, params) and return a QCircuit.
+ Default is hardware_efficient_circuit.
+ layers : ``int``, ``optional``\n
+ Number of layers in the default hardware-efficient ansatz. Default is 1.
+ Ignored if custom ansatz is provided.
+ optimizer : ``str``, ``optional``\n
+ Classical optimizer to use. Should be one of scipy.optimize.minimize methods.
+ Default is 'COBYLA'.
+ initial_params : ``array-like``, ``optional``\n
+ Initial parameters for the ansatz circuit. If None, random initialization is used.
+
+ Attributes
+ n_qubits : ``int``\n
+ Number of qubits in the system.
+ energy_history : ``list``\n
+ List of energy values during optimization.
+ optimal_params : ``array-like``\n
+ Optimized parameters after running VQE.
+ optimal_energy : ``float``\n
+ Ground state energy found by VQE.
+
+ Methods
+ run : Run the VQE algorithm to find the ground state energy.
+ compute_energy : Compute the energy expectation value for given parameters.
+
+ References
+ [1] Peruzzo A, McClean J, Fowler P, et al. A variational eigenvalue solver on a
+ photonic quantum processor[J]. Nature Communications, 2014, 5: 4213.
+ https://doi.org/10.1038/ncomms5213
+
+ [2] Cerezo M, Arrasmith A, Babbush R, et al. Variational quantum algorithms[J].
+ Nature Reviews Physics, 2021, 3(9): 625-644.
+ https://doi.org/10.1038/s42254-021-00348-9
+
+ Examples
+ >>> from pyqpanda_alg import VQE
+ >>> from pyqpanda3.hamiltonian import PauliOperator
+ >>> # Define a simple Hamiltonian: H = 0.5 * Z0 + 0.3 * X0 * X1
+ >>> hamiltonian = PauliOperator({"Z0": 0.5, "X0 X1": 0.3})
+ >>> # Create VQE solver
+ >>> vqe = VQE.VQE(hamiltonian, layers=2)
+ >>> # Run VQE
+ >>> energy, params = vqe.run()
+ >>> print(f"Ground state energy: {energy}")
+ """
+
+ def __init__(self,
+ hamiltonian: Union[PauliOperator, Hamiltonian],
+ ansatz: Optional[Callable] = None,
+ layers: int = 1,
+ optimizer: str = 'COBYLA',
+ initial_params: Optional[np.ndarray] = None):
+
+ # Process hamiltonian
+ if isinstance(hamiltonian, Hamiltonian):
+ self.hamiltonian = hamiltonian.pauli_operator()
+ elif isinstance(hamiltonian, PauliOperator):
+ self.hamiltonian = hamiltonian
+ else:
+ raise TypeError("hamiltonian must be a PauliOperator or Hamiltonian")
+
+ # Determine number of qubits
+ self.n_qubits = self._get_n_qubits(self.hamiltonian)
+
+ # Set up ansatz
+ if ansatz is None:
+ self.ansatz = hardware_efficient_circuit
+ self.layers = layers
+ self.n_params = 2 * self.n_qubits * layers
+ else:
+ self.ansatz = ansatz
+ self.layers = None
+ # Try to infer number of parameters
+ test_params = np.zeros(100) # Large enough for most cases
+ try:
+ test_circuit = self.ansatz(list(range(self.n_qubits)), test_params[:self.n_qubits*2])
+ self.n_params = len(test_params[:self.n_qubits*2])
+ except:
+ # If inference fails, user must provide initial_params
+ self.n_params = None
+
+ # Set up optimizer
+ self.optimizer = optimizer
+
+ # Set initial parameters
+ if initial_params is not None:
+ self.initial_params = np.array(initial_params)
+ if self.n_params is None:
+ self.n_params = len(self.initial_params)
+ else:
+ if self.n_params is None:
+ raise ValueError("Cannot determine number of parameters. Please provide initial_params.")
+ self.initial_params = np.random.uniform(-np.pi, np.pi, self.n_params)
+
+ # Initialize tracking variables
+ self.energy_history = []
+ self.optimal_params = None
+ self.optimal_energy = None
+
+ # Initialize quantum machine
+ self.machine = CPUQVM()
+
+ def _get_n_qubits(self, hamiltonian: PauliOperator) -> int:
+ """Extract the number of qubits from the hamiltonian."""
+ max_qubit = -1
+ for term in hamiltonian.terms():
+ for pauli in term.paulis():
+ qubit_idx = pauli.qbit()
+ if qubit_idx > max_qubit:
+ max_qubit = qubit_idx
+ return max_qubit + 1
+
+ def _build_circuit(self, params: np.ndarray) -> QCircuit:
+ """Build the parameterized quantum circuit."""
+ qubit_list = list(range(self.n_qubits))
+ return self.ansatz(qubit_list, params)
+
+ def compute_energy(self, params: np.ndarray) -> float:
+ """
+ Compute the energy expectation value <ψ(θ)|H|ψ(θ)> for given parameters.
+
+ Parameters
+ params : ``array-like``\n
+ Parameters for the ansatz circuit.
+
+ Returns
+ energy : ``float``\n
+ Energy expectation value.
+
+ Examples
+ >>> from pyqpanda_alg import VQE
+ >>> from pyqpanda3.hamiltonian import PauliOperator
+ >>> import numpy as np
+ >>> hamiltonian = PauliOperator({"Z0": 0.5, "X0 X1": 0.3})
+ >>> vqe = VQE.VQE(hamiltonian, layers=2)
+ >>> params = np.random.random(vqe.n_params)
+ >>> energy = vqe.compute_energy(params)
+ >>> print(f"Energy: {energy}")
+ """
+ # Build the ansatz circuit
+ ansatz_circuit = self._build_circuit(params)
+
+ # Calculate energy expectation value
+ energy = 0.0
+
+ for term in self.hamiltonian.terms():
+ coef = term.coef().real
+ paulis = term.paulis()
+
+ if len(paulis) == 0:
+ # Identity term
+ energy += coef
+ continue
+
+ # Build measurement circuit for this term
+ prog = QProg(self.n_qubits)
+ qubit_list = prog.qubits()
+
+ # Apply ansatz
+ prog << ansatz_circuit
+
+ # Apply basis rotation for measurement
+ measurement_circuit = self._get_measurement_circuit(paulis, qubit_list)
+ prog << measurement_circuit
+
+ # Run and get probabilities
+ self.machine.run(prog, shots=1)
+ prob_dict = self.machine.result().get_prob_dict(qubit_list)
+
+ # Calculate expectation value
+ expectation = self._calculate_expectation(prob_dict, paulis)
+ energy += coef * expectation
+
+ self.energy_history.append(energy)
+ return energy
+
+ def _get_measurement_circuit(self, paulis, qubit_list) -> QCircuit:
+ """Get the circuit to rotate to the measurement basis."""
+ circuit = QCircuit()
+
+ for pauli in paulis:
+ qubit_idx = pauli.qbit()
+ pauli_type = pauli.pauli_char()
+
+ if pauli_type == 'X':
+ # Rotate from X basis to Z basis
+ from pyqpanda3.core import H
+ circuit << H(qubit_list[qubit_idx])
+ elif pauli_type == 'Y':
+ # Rotate from Y basis to Z basis
+ from pyqpanda3.core import RX
+ circuit << RX(qubit_list[qubit_idx], -np.pi / 2)
+ # Z basis doesn't need rotation
+
+ return circuit
+
+ def _calculate_expectation(self, prob_dict: dict, paulis) -> float:
+ """Calculate the expectation value from measurement probabilities."""
+ expectation = 0.0
+
+ for state, prob in prob_dict.items():
+ # Calculate parity for the measured qubits
+ parity = 1
+ for pauli in paulis:
+ qubit_idx = pauli.qbit()
+ # State string is in reverse order (qubit 0 is rightmost)
+ bit = int(state[-(qubit_idx + 1)])
+ parity *= (-1) ** bit
+
+ expectation += prob * parity
+
+ return expectation
+
+ def run(self, optimizer_options: Optional[dict] = None) -> tuple:
+ """
+ Run the VQE algorithm to find the ground state energy.
+
+ Parameters
+ optimizer_options : ``dict``, ``optional``\n
+ Options to pass to the scipy optimizer.
+
+ Returns
+ optimal_energy : ``float``\n
+ The ground state energy found by VQE.
+ optimal_params : ``array-like``\n
+ The optimized parameters for the ansatz circuit.
+
+ Examples
+ >>> from pyqpanda_alg import VQE
+ >>> from pyqpanda3.hamiltonian import PauliOperator
+ >>> hamiltonian = PauliOperator({"Z0": 0.5, "X0 X1": 0.3})
+ >>> vqe = VQE.VQE(hamiltonian, layers=2)
+ >>> energy, params = vqe.run()
+ >>> print(f"Ground state energy: {energy}")
+ >>> print(f"Optimal parameters: {params}")
+ """
+ if optimizer_options is None:
+ optimizer_options = {}
+
+ # Clear history
+ self.energy_history = []
+
+ # Define objective function
+ def objective(params):
+ return self.compute_energy(params)
+
+ # Run optimization
+ result = minimize(
+ objective,
+ self.initial_params,
+ method=self.optimizer,
+ options=optimizer_options
+ )
+
+ self.optimal_params = result.x
+ self.optimal_energy = result.fun
+
+ return self.optimal_energy, self.optimal_params
diff --git a/pyqpanda-algorithm/pyqpanda_alg/__init__.py b/pyqpanda-algorithm/pyqpanda_alg/__init__.py
index 12d6808..6e99e2f 100644
--- a/pyqpanda-algorithm/pyqpanda_alg/__init__.py
+++ b/pyqpanda-algorithm/pyqpanda_alg/__init__.py
@@ -30,7 +30,7 @@
warnings.filterwarnings("ignore", category=SyntaxWarning)
from . import QAOA
-# from . import VQE
+from . import VQE
# from . import HHL
# from . import QAOA
from . import QARM
diff --git a/pyqpanda-algorithm/test/VQE/Test_VQE.py b/pyqpanda-algorithm/test/VQE/Test_VQE.py
new file mode 100644
index 0000000..4d20d58
--- /dev/null
+++ b/pyqpanda-algorithm/test/VQE/Test_VQE.py
@@ -0,0 +1,139 @@
+# 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 pytest
+import numpy as np
+from pyqpanda3.hamiltonian import PauliOperator
+from pyqpanda_alg.VQE import VQE, hardware_efficient_circuit
+
+
+class TestVQE:
+ """Test cases for VQE algorithm."""
+
+ def test_vqe_initialization(self):
+ """Test VQE initialization with PauliOperator."""
+ hamiltonian = PauliOperator({"Z0": 0.5, "X0 X1": 0.3})
+ vqe = VQE(hamiltonian, layers=2)
+ assert vqe.n_qubits == 2
+ assert vqe.n_params == 8 # 2 qubits * 2 layers * 2 params per qubit
+
+ def test_vqe_with_custom_ansatz(self):
+ """Test VQE with custom ansatz circuit."""
+ hamiltonian = PauliOperator({"Z0": 1.0})
+
+ def custom_ansatz(qubit_list, params):
+ from pyqpanda3.core import QCircuit, RY
+ circuit = QCircuit()
+ for i, q in enumerate(qubit_list):
+ circuit << RY(q, params[i])
+ return circuit
+
+ vqe = VQE(hamiltonian, ansatz=custom_ansatz, initial_params=np.array([0.5]))
+ assert vqe.n_qubits == 1
+ assert vqe.n_params == 1
+
+ def test_hardware_efficient_circuit(self):
+ """Test hardware efficient ansatz circuit construction."""
+ qubits = [0, 1, 2]
+ params = np.random.random(2 * 3 * 2) # 3 qubits, 2 layers, 2 params per qubit per layer
+ circuit = hardware_efficient_circuit(qubits, params, layers=2)
+ assert circuit is not None
+
+ def test_vqe_compute_energy(self):
+ """Test energy computation for given parameters."""
+ hamiltonian = PauliOperator({"Z0": 1.0})
+ vqe = VQE(hamiltonian, layers=1)
+ params = np.zeros(vqe.n_params)
+ energy = vqe.compute_energy(params)
+ # For Z0 with zero parameters, should be close to 1.0 (|0⟩ state)
+ assert isinstance(energy, float)
+
+ def test_vqe_run_simple_hamiltonian(self):
+ """Test VQE optimization on a simple Hamiltonian."""
+ # Simple Hamiltonian: H = Z0
+ # Ground state should be |1⟩ with energy -1
+ hamiltonian = PauliOperator({"Z0": 1.0})
+ vqe = VQE(hamiltonian, layers=2, optimizer='COBYLA')
+
+ energy, params = vqe.run(optimizer_options={'maxiter': 50})
+
+ # Energy should be close to -1 (ground state of Z)
+ assert energy < 0
+ assert abs(energy - (-1.0)) < 0.5 # Allow some tolerance for variational method
+
+ def test_vqe_two_qubit_hamiltonian(self):
+ """Test VQE on a two-qubit Hamiltonian."""
+ # H = 0.5 * Z0 + 0.5 * Z1 + 0.3 * X0 * X1
+ hamiltonian = PauliOperator({
+ "Z0": 0.5,
+ "Z1": 0.5,
+ "X0 X1": 0.3
+ })
+ vqe = VQE(hamiltonian, layers=3, optimizer='COBYLA')
+
+ energy, params = vqe.run(optimizer_options={'maxiter': 100})
+
+ # Energy should be negative (ground state)
+ assert energy < 0
+ assert len(vqe.energy_history) > 0
+
+ def test_vqe_energy_history(self):
+ """Test that VQE tracks energy history during optimization."""
+ hamiltonian = PauliOperator({"Z0": 1.0})
+ vqe = VQE(hamiltonian, layers=1)
+
+ energy, params = vqe.run(optimizer_options={'maxiter': 10})
+
+ assert len(vqe.energy_history) > 0
+ assert vqe.optimal_energy == energy
+ assert vqe.optimal_params is not None
+
+ def test_invalid_hamiltonian_type(self):
+ """Test that invalid hamiltonian type raises error."""
+ with pytest.raises(TypeError):
+ VQE("not a hamiltonian")
+
+ def test_invalid_params_length(self):
+ """Test that invalid parameter length raises error."""
+ hamiltonian = PauliOperator({"Z0": 1.0})
+ qubits = [0]
+ params = np.zeros(10) # Wrong length
+
+ with pytest.raises(ValueError):
+ hardware_efficient_circuit(qubits, params, layers=2)
+
+
+class TestHardwareEfficientCircuit:
+ """Test cases for hardware efficient ansatz."""
+
+ def test_single_qubit_single_layer(self):
+ """Test single qubit, single layer circuit."""
+ qubits = [0]
+ params = [0.5, 0.3] # RY and RZ angles
+ circuit = hardware_efficient_circuit(qubits, params, layers=1)
+ assert circuit is not None
+
+ def test_multi_qubit_multi_layer(self):
+ """Test multiple qubits and layers."""
+ qubits = [0, 1, 2]
+ n_params = 2 * 3 * 3 # 3 qubits, 3 layers, 2 params per qubit per layer
+ params = np.random.random(n_params)
+ circuit = hardware_efficient_circuit(qubits, params, layers=3)
+ assert circuit is not None
+
+ def test_circuit_with_entanglement(self):
+ """Test that circuit includes entangling gates."""
+ qubits = [0, 1]
+ params = np.random.random(2 * 2 * 2) # 2 qubits, 2 layers
+ circuit = hardware_efficient_circuit(qubits, params, layers=2)
+ # Circuit should be created successfully
+ assert circuit is not None
From 711955d434a8d556814c194f13796c9aff103036 Mon Sep 17 00:00:00 2001
From: jsjjl
Date: Mon, 15 Jun 2026 09:48:25 +0000
Subject: [PATCH 11/11] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20Grover=20?=
=?UTF-8?q?=E7=AE=97=E6=B3=95=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 amp_operator 中 in_operator 被重复调用的 bug(dagger 和正向电路现在共享同一实例)
- 提取 _ensure_list 辅助函数,消除 cir 与 amp_operator 中重复的 qubit 列表处理逻辑
- 移除 mark_data_reflection 中无意义的 BARRIER 操作,简化电路
- 修复 GroverAdaptiveSearch 中冗余的三元表达式和 minimum_indexes 去重逻辑
- 为 cir 方法添加 iternum 负值校验
- 新增 Grover.search() 便捷方法,一键完成电路构建、执行和结果返回
- 修正 Grover __init__.py 模块文档字符串
- 新增 test/Grover/Test_Grover.py 测试文件,覆盖 24 个测试用例
---
.../pyqpanda_alg/Grover/Grover_core.py | 1231 +++++++++--------
.../pyqpanda_alg/Grover/__init__.py | 3 +-
pyqpanda-algorithm/test/Grover/Test_Grover.py | 295 ++++
3 files changed, 930 insertions(+), 599 deletions(-)
create mode 100644 pyqpanda-algorithm/test/Grover/Test_Grover.py
diff --git a/pyqpanda-algorithm/pyqpanda_alg/Grover/Grover_core.py b/pyqpanda-algorithm/pyqpanda_alg/Grover/Grover_core.py
index 0d2c2b3..6faa13e 100644
--- a/pyqpanda-algorithm/pyqpanda_alg/Grover/Grover_core.py
+++ b/pyqpanda-algorithm/pyqpanda_alg/Grover/Grover_core.py
@@ -1,598 +1,633 @@
-# 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.
-
-from pyqpanda3.core import CPUQVM, QCircuit, QProg, Z, X, H, BARRIER
-import numpy as np
-
-from .. plugin import *
-
-class Grover:
- """ This class provides a framework for Grover Search algorithm [1].
-
- Parameters
- in_operator : callable ``f(qubits)``\n
- Operator/Circuit of the initial search state for the algorithm, default Hadamards.
- flip_operator : callable ``f(qubits)``\n
- Operator/Circuit of marking the good states by phase-flip. Default doing a pauli-Z
- gate at the last qubit.
- zero_flip : callable ``f(qubits)``\n
- Operator/Circuit of reflects 0s by phase-flip. Default doing a zero-controled pauli-Z
- gate on qubits.
- mark_data : ``str``, ``list[str]``\n
- Marked target state. Default None.
- Only used when simply marking a known query state, as the designed flip_operator part.
- amplify_operator : callable ``f(qubits)``\n
- Constructed complete Grover amplitude amplification operator circuit. Default None.
- For users' special designed amplitude amplification operator.
-
- References
- [1] L. K. Grover, A fast quantum mechanical algorithm for database search. Proceedings
- 28th Annual Symposium on the Theory of Computing (STOC) 1996, pp. 212-219.
- https://arxiv.org/abs/quant-ph/9605043
-
- """
- def __init__(self,
- in_operator=None,
- flip_operator=None,
- zero_flip=None,
- mark_data=None,
- amplify_operator=None):
- self.in_operator = hadamard_circuit if in_operator is None else in_operator
-
- if flip_operator is None and mark_data is not None:
- def s_f(q_num):
- return mark_data_reflection(q_num, mark_data)
- self.flip_operator = s_f
- else:
- self.flip_operator = flip_operator
- self.u_s = zero_flip
- self.amplify = amplify_operator
-
- def cir(self, q_input=None, q_flip=None, q_zero=None, iternum: int = 1):
- """
- Get full circuit of Grover search.
-
- Parameters
- q_input : ``QVec``\n
- Target qubit(s) for in_operator (initial preparation circuit).
- Using Hadamard gates to create the uniform superposition at the beginning most of time.
- Although in most simple cases it includes the full workspace qubits,
- auxiliary qubits can be excluded when dealing with some complex problems.
- q_flip : ``QVec``\n
- Target qubit(s) for flip_operator.
- q_zero : ``QVec``\n
- Target qubit(s) for zero_flip.
- iternum : ``int``\n
- The number of iterations. In another word number of repetition of applying the Grover operator.
-
- Returns
- circuit : ``QCircuit``\n
- Full quantum circuit for given Grover search.
-
- Examples
- An example for implementing an Grover search for state where q_0 `and` q_1 is 1.
-
- >>> from pyqpanda3.core import CPUQVM, QCircuit, Z, TOFFOLI
- >>> from pyqpanda_alg import Grover
- >>> m = CPUQVM()
- >>> q_state = list(range(3))
-
- >>> def mark(qubits):
- >>> cir = QCircuit()
- >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
- >>> cir << Z(qubits[2])
- >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
- >>> return cir
-
- >>> demo_search = Grover.Grover(flip_operator=mark)
- >>> prog = QProg()
- >>> prog << demo_search.cir(q_input=q_state[:2], q_flip=q_state, q_zero=q_state[:2], iternum=1)
- >>> m.run(prog,1000)
- >>> res = m.result().get_prob_dict(q_state[:2])
- >>> print(res)
- >>> print(prog)
- {'00': 0.0, '01': 0.0, '10': 0.0, '11': 1.0000000000000004}
-
- .. parsed-literal::
- ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
- q_0: |0>─┤H├ ─■─ ─── ─■─ ┤H├ ┤X├ ─■─ ┤X├ ┤H├
- ├─┤ │ │ ├─┤ ├─┤ ┌┴┐ ├─┤ ├─┤
- q_1: |0>─┤H├ ─■─ ─── ─■─ ┤H├ ┤X├ ┤Z├ ┤X├ ┤H├
- └─┘ ┌┴┐ ┌─┐ ┌┴┐ └─┘ └─┘ └─┘ └─┘ └─┘
- q_2: |0>──── ┤X├ ┤Z├ ┤X├ ─── ─── ─── ─── ───
- └─┘ └─┘ └─┘
-
- """
- if not hasattr(q_input, '__len__'):
- q_input = [q_input]
-
- if q_flip is None:
- q_flip = q_input
- if not hasattr(q_flip, '__len__'):
- q_flip = [q_flip]
-
- if q_zero is None:
- q_zero = q_input
- if not hasattr(q_zero, '__len__'):
- q_zero = [q_zero]
-
- q_all = list(set(q_input + q_zero + q_flip))
- if self.amplify is not None:
- amp_cir = self.amplify(q_all)
- else:
- amp_cir = amp_operator(q_input, q_flip, q_zero, self.in_operator, self.flip_operator, self.u_s)
-
- circuit = QCircuit()
- circuit << self.in_operator(q_input)
- while iternum > 0:
- circuit << amp_cir
- iternum -= 1
- return circuit
-
-
-def iter_num(q_num, sol_num):
- """
- Calculate the optimal number of iterations in Grover search.
-
- Parameters
- q_num : ``int``\n
- The number of qubits in the search space. Search space size: :math:`N = 2 ^ {\\text {q_num}}`.
- sol_num : ``int``\n
- Number of target solution states.
-
- Returns
- num : The optimal number of iterations in Grover search.
-
- Examples
- An example for the case we show in the Grover search circuit. And we know there
- is only one solution to be found. And total 2 qubits for the search space.
-
- >>> from pyqpanda3.core import CPUQVM, QCircuit, Z, TOFFOLI
- >>> from pyqpanda_alg import Grover
- >>> m = CPUQVM()
- >>> q_state = list(range(3))
-
- >>> def mark(qubits):
- >>> cir = QCircuit()
- >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
- >>> cir << Z(qubits[2])
- >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
- >>> return cir
- >>> demo_search = Grover.Grover(flip_operator=mark)
- >>> iter_num = Grover.iter_num(q_num=len(q_state), sol_num=2)
- >>> print('best iter num: ', iter_num)
- best iter num: 1
-
- """
- num = int(np.floor(np.pi * np.sqrt(2 ** q_num / sol_num) / 4))
- return num
-
-
-def iter_analysis(q_num, sol_num, iternum=1):
- """
- Calculate the amplification probability and rotation angle for given amplitude
- amplification iteration number.
-
- Parameters
- q_num : ``int``\n
- The number of qubits in the search space. Search space size: :math:`N = 2 ^ {\\text {q_num}}`.
- sol_num : ``int``\n
- Number of target solution states.
- iternum : ``int``\n
- Given number of iteration.
-
- Returns
- prob, theta : (``float``, ``float``)\n
- The amplification probability and rotation angle for given iteration.
-
- Examples
- An example for the case we show in the Grover search circuit. And we know there
- is only one solution to be found. And total 2 qubits for the search space.
-
- >>> from pyqpanda3.core import CPUQVM, QCircuit, Z, TOFFOLI
- >>> from pyqpanda_alg import Grover
- >>> m = CPUQVM()
- >>> q_state = list(range(3))
-
- >>> def mark(qubits):
- >>> cir = QCircuit()
- >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
- >>> cir << Z(qubits[2])
- >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
- >>> return cir
- >>> demo_search = Grover.Grover(flip_operator=mark)
- >>> prob, angle = Grover.iter_analysis(q_num=len(q_state), sol_num=2, iternum=1)
- >>> print('prob for getting one of the solution with given iter num 1:', prob)
- >>> prob, angle = Grover.iter_analysis(q_num=len(q_state), sol_num=2, iternum=2)
- >>> print('prob for getting one of the solution with given iter num 2:', prob)
- prob for getting one of the solution with given iter num 1: 1.0
- prob for getting one of the solution with given iter num 2: 0.24999999999999956
-
- """
- sol_prob = sol_num / (2 ** q_num)
- theta = np.arcsin(np.sqrt(sol_prob)) * 2
- prob = np.sin(((2 * iternum + 1) / 2) * theta) ** 2
- return prob, theta
-
-
-
-def amp_operator(q_input=None, q_flip=None, q_zero=None, in_operator=None, flip_operator=None, zero_flip=None):
- """
- Construct complete Grover amplitude amplification operator.
- Can be part of Grover/Quantum Count/QAE and other amplitude amplification related algorithm.
-
- Parameters
- q_input : ``QVec``\n
- Target qubit(s) for in_operator (initial preparation circuit).
- Using Hadamard gates to create the uniform superposition at the beginning most of time.
- Although in most simple cases it includes the full workspace qubits,
- auxiliary qubits can be excluded when dealing with some complex problems.
- q_flip : ``QVec``\n
- Target qubit(s) for flip_operator.
- q_zero : ``QVec``\n
- Target qubit(s) for zero_flip.
- in_operator : callable ``f(qubits)``\n
- Operator/Circuit of the initial search state for the algorithm, default Hadamards.
- flip_operator : callable ``f(qubits)``\n
- Operator/Circuit of marking the good states by phase-flip. Default doing a pauli-Z
- gate at the last qubit.
- zero_flip : callable ``f(qubits)``\n
- Operator/Circuit of reflects 0s by phase-flip. Default doing a zero-controled pauli-Z
- gate on qubits.
-
- Returns
- circuit : QCircuit\n
- Amplitude amplification operator.
-
- Examples
- An example for constucting a amplitude amplification operator used in the case we show
- in the Grover search circuit.
-
- >>> from pyqpanda3.core import CPUQVM, QCircuit, Z, TOFFOLI
- >>> from pyqpanda_alg import Grover
- >>> m = CPUQVM()
- >>> q_state = list(range(3))
-
- >>> def mark(qubits):
- >>> cir = QCircuit()
- >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
- >>> cir << Z(qubits[2])
- >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
- >>> return cir
- >>> print(Grover.amp_operator(q_input=q_state[:2], q_flip=q_state, q_zero=q_state[:2], flip_operator=mark))
-
- .. parsed-literal::
- ┌─┐ ┌─┐ ┌─┐ ┌─┐
- q_0: |0>──■─ ─── ─■─ ┤H├ ┤X├ ─■─ ┤X├ ┤H├
- │ │ ├─┤ ├─┤ ┌┴┐ ├─┤ ├─┤
- q_1: |0>──■─ ─── ─■─ ┤H├ ┤X├ ┤Z├ ┤X├ ┤H├
- ┌┴┐ ┌─┐ ┌┴┐ └─┘ └─┘ └─┘ └─┘ └─┘
- q_2: |0>─┤X├ ┤Z├ ┤X├ ─── ─── ─── ─── ───
- └─┘ └─┘ └─┘
-
- """
- if not hasattr(q_input, '__len__'):
- q_input = [q_input]
-
- if q_flip is None:
- q_flip = [q_input[-1]]
- if not hasattr(q_flip, '__len__'):
- q_flip = [q_flip]
-
- if q_zero is None:
- q_zero = q_input
- if not hasattr(q_zero, '__len__'):
- q_zero = [q_zero]
-
- in_operator_no_dagger = in_operator(q_input) if in_operator is not None else apply_QGate(q_input, H)
- in_operator = in_operator(q_input) if in_operator is not None else apply_QGate(q_input, H)
-
- flip_operator = flip_operator(q_flip) if flip_operator is not None else Z(q_flip[-1])
-
- if zero_flip is not None:
- zero_flip = zero_flip(q_zero)
- elif len(q_zero) == 1:
- zero_flip = QCircuit()
- zero_flip << X(q_zero[0]) << Z(q_zero[0]) << X(q_zero[0])
- else:
- zero_flip = QCircuit()
- zero_flip << apply_QGate(q_zero, X)
- zero_flip << apply_QGate([q_zero[-1]], Z).control(q_zero[:-1])
- zero_flip << apply_QGate(q_zero, X)
-
- circuit = QCircuit()
- circuit << flip_operator
- circuit << in_operator.dagger() << zero_flip << in_operator_no_dagger
-
- return circuit
-
-
-def mark_data_reflection(qubits: list = None, mark_data=None):
- """
- Can be used to construct a phase flip operator for given target states.
-
- Parameters
- qubits : ``QVec``\n
- Target qubit(s) for flip_operator.
- mark_data : ``str``, ``list[str]``\n
- Marked target state(s).
-
- Returns
- flip_operator : ``QCircuit``\n
- A phase flip operator for given target states
-
- Examples
- An example for searching '101' and '001' using the flip operator given by this function.
-
- >>> from pyqpanda3.core import CPUQVM, QProg
- >>> from pyqpanda_alg import Grover
- >>> m = CPUQVM()
-
- >>> q_state = list(range(3))
- >>> def mark(qubits):
- >>> return Grover.mark_data_reflection(qubits=qubits, mark_data=['101', '001'])
- >>> demo_search = Grover.Grover(flip_operator=mark)
- >>> prog = QProg()
- >>> prog << demo_search.cir(q_input=q_state)
- >>> m.run(prog,1000)
- >>> res = m.result().get_prob_dict(q_state)
- >>> print(res)
- {'000': 0.0, '001': 0.5000000000000002, '010': 0.0, '011': 0.0, '100': 0.0, '101': 0.5000000000000002, '110': 0.0, '111': 0.0}
-
- """
- if not hasattr(qubits, '__len__'):
- qubits = [qubits]
- flip_operator = QCircuit()
-
- if isinstance(mark_data, str):
- mark_data = [mark_data]
- n = len(qubits)
-
- for i in mark_data:
- for j in range(n):
- if i[-(j + 1)] == '0':
- flip_operator << X(qubits[j])
- else:
- flip_operator << BARRIER([qubits[j]])
- flip_operator << Z(qubits[-1]).control(qubits[:-1])
-
- for j in range(n):
- if i[-(j + 1)] == '0':
- flip_operator << X(qubits[j])
- else:
- flip_operator << BARRIER([qubits[j]])
- return flip_operator
-
-
-class GroverAdaptiveSearch:
- """This class provides a framework for Grover Adaptive Search [2].
-
- Parameters
- init_value : ``float``\n
- The given initial value of the optimization function.
- n_index : ``int``\n
- The number of qubits in the search space. Search space size: N = 2 ** q_num.
- init_circuit : callable ``f(qubits)``\n
- Operator/Circuit of the initial search state for the algorithm, default Hadamards.
- oracle_circuit : callable ``f(qubits, value)``\n
- Operator/Circuit of marking the `better` states by phase-flip. Default doing a pauli-Z
- gate at the last qubit.
-
- References
- [2] A. Gilliam, S. Woerner, C. Gonciulea, Grover Adaptive Search for Constrained
- Polynomial Binary Optimization. https://arxiv.org/abs/1912.04088
-
- >>> from pyqpanda_alg import Grover
- >>> import numpy as np
- >>> from pyqpanda3.core import QCircuit, H, U1, SWAP
-
- >>> def qft(qbs):
- >>> n = len(qbs)
- >>> qbs = qbs[::-1]
- >>> cir = QCircuit()
- >>> for j in range(1, n):
- >>> cir << H(qbs[j - 1])
- >>> for i in range(j, n):
- >>> cir << U1(qbs[j - 1], np.pi * 2 ** (j - 1 - i)).control(qbs[i])
- >>> cir << H(qbs[-1])
- >>> for x in range(int(len(qbs)/2)):
- >>> cir << SWAP(qbs[x], qbs[-x-1])
- >>> return cir
-
- >>> # flip if x0 * x1 + x0 - x1 - current_min < 0
- >>> def flip_oracle_function(q_index_value, current_min):
- >>> q_index = q_index_value[:2]
- >>> q_value = q_index_value[2:]
- >>> n_value = len(q_value)
- >>> factor = np.pi * 2 ** (1 - n_value)
- >>> cal_cir = QCircuit()
- >>> for i, q_i in enumerate(q_value):
- >>> cal_cir << H(q_i)
- >>> cal_cir << U1(q_i, factor * 2 ** i).control(q_index)
- >>> cal_cir << U1(q_i, factor * 2 ** i).control(q_index[0])
- >>> cal_cir << U1(q_i, -factor * 2 ** i).control(q_index[1])
- >>> cal_cir << U1(q_i, factor * 2 ** i * (-current_min))
- >>> cal_cir << qft(q_value).dagger()
- >>> return cal_cir
-
- >>> demo_search = Grover.GroverAdaptiveSearch(init_value=0, n_index=2, oracle_circuit=flip_oracle_function)
-
- >>> def n_value_function(current_min):
- >>> n_value = 2 if current_min == 0 else 3
- >>> return n_value
-
- >>> def value_function(var_array):
- >>> var_array = list(map(int, var_array))[::-1]
- >>> value = var_array[0] * var_array[1] + var_array[0] - var_array[1]
- >>> return value
-
- >>> res = demo_search.run(continue_times=3,
- >>> n_value_function=n_value_function,
- >>> value_function=value_function,
- >>> process_show=True)
- >>> print(res)
- ======searching 1 ,rotation = 1 ======
- minimum Key Again: 00
- minimum Value No Change: 0
- ======searching 2 ,rotation = 1 ======
- Current minimum Key: 10
- Current minimum Value: -1
- ======searching 1 ,rotation = 1 ======
- minimum Key Again: 10
- minimum Value No Change: -1
- ======searching 2 ,rotation = 1 ======
- ======searching 3 ,rotation = 1 ======
- rotations: 5
- ([[0, 1]], -1)
-
- """
- def __init__(self, init_value, n_index, init_circuit=None, oracle_circuit=None):
- self._current_min = init_value
- self.n_index = n_index
- self.n_value = None
- self.init_circuit = init_circuit
- self.oracle_circuit = oracle_circuit
-
- def _init_circuit(self, qlist):
- if self.init_circuit is None:
- return hadamard_circuit(qlist[:self.n_index])
- else:
- return self.init_circuit(qlist, self._current_min)
-
- def _oracle_circuit(self, qlist):
- if self.oracle_circuit is None:
- return Z(qlist[-1])
- else:
- return self.oracle_circuit(qlist, self._current_min)
-
- def run(self, continue_times: int = 3, n_value_function=None, value_function=None,
- rotation_change='random', process_show=False):
- """
- Run the Grover Adaptive Search algorithm to find the minimum.
-
- Parameters
- continue_times : ``int``\n
- The maximum number of repeated searches at the current optimal point.
- n_value_function : callable ``f(value)``\n
- Function for computing the number of qubits for marking the `better` states at current
- best value, variable qubits not included.
- value_function : callable ``f(var_array)``\n
- Function for computing the problem value of given varriables array(str given as qpanda state).
- rotation_change : ``str{'random', 'increase'}``, optional\n
- The method to get the number of Grover iterations for each search of a search cycle.
-
- - ``random`` : The number of Grover iterations for each search is randomly obtained from a
- increasing interval. (Default)
- - ``increase`` : The number of Grover iterations for each search is increasing.
- process_show : ``bool``\n
- Set to True to print the detail during search.
-
- Returns
- minimum_indexes, minimum_res : ( ``list[list[int]]``, ``float``)\n
- The optimization result including the solution array and the optimal value.
-
- Examples
- An example for minimization of quadratic binary function: x0 * x1 + x0 - x1.
-
-
- """
- binary = True
- num_all_solutions = 2 ** self.n_index
- machine = CPUQVM()
-
- minimum_found = False
- minimum_res = self._current_min
- minimum_indexes = []
- indexes_measured = []
-
- rotations = 0
-
- while not minimum_found:
- m = 1
- improvement_found = False
- loops_with_no_improvement = 0
- while not improvement_found:
- rotations += 1
- self.n_value = n_value_function(self._current_min)
-
- q_index_value = QProg(self.n_index + self.n_value).qubits()
-
- if rotation_change == 'random':
- rotation_count = 1 if m < 2 else np.random.randint(low=1, high=m)
- elif rotation_change == 'increase':
- rotation_count = int(m)
- else:
- raise NameError("Method not recognized")
-
- if process_show:
- print('======searching', loops_with_no_improvement + 1, ',rotation =', rotation_count, '======')
- Grover_instance = Grover(in_operator=self._init_circuit, flip_operator=self._oracle_circuit)
-
- zxy = Grover_instance.cir(q_input=q_index_value, q_flip=q_index_value, q_zero=q_index_value[:self.n_index],
- iternum=rotation_count)
-
- prog = QProg()
- prog << zxy << measure_all(q_index_value[:self.n_index], q_index_value[:self.n_index])
- machine.run(prog, shots=1)
- prog_res = machine.result().get_prob_dict()
- outcome = list(prog_res.keys())[0]
-
- current_value = value_function(outcome)
- v = current_value - self._current_min
-
- if v < 0:
- indexes_measured.append(outcome)
- minimum_res = current_value
- minimum_indexes = [outcome]
- if process_show:
- print('Current minimum Key: ', outcome)
- print('Current minimum Value: ', minimum_res)
- improvement_found = True
- self._current_min = minimum_res
-
-
- else:
- loops_with_no_improvement += 1
- if outcome not in indexes_measured:
- indexes_measured.append(outcome)
- if v == 0:
- if outcome not in indexes_measured:
- minimum_indexes.append(outcome)
- if process_show:
- print('minimum Key Again: ', outcome)
- print('minimum Value No Change: ', minimum_res)
- m = min(m * 1.34, 2 ** (self.n_index / 2)) if rotation_change == 'random' \
- else min(m * 1.34, 2 ** (self.n_index / 2))
- if loops_with_no_improvement >= continue_times or \
- len(indexes_measured) == num_all_solutions:
- improvement_found = True
- minimum_found = True
-
- minimum_indexes = list(set(minimum_indexes))
- if binary:
- opt_xs = []
- for key in minimum_indexes:
- opt_x = np.array([1 if s == '1' else 0 for s in ('{0:%s}' % self.n_index).format(key)])
- opt_xs.append(opt_x.tolist()[::-1])
- minimum_indexes = opt_xs
- if process_show:
- print('rotations: ', rotations)
- return minimum_indexes, minimum_res
-
- @staticmethod
- def _bin_to_int(bin_value):
- n_value = len(bin_value)
- if bin_value.startswith("1"):
- int_v = int(bin_value, 2) - 2 ** n_value
- else:
- int_v = int(bin_value, 2)
- return int_v
+# 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.
+
+from pyqpanda3.core import CPUQVM, QCircuit, QProg, Z, X, H, BARRIER
+import numpy as np
+
+from ..plugin import *
+
+
+def _ensure_list(q):
+ """Normalize a qubit argument to a list."""
+ if q is None:
+ return None
+ if not hasattr(q, '__len__'):
+ return [q]
+ return list(q)
+
+
+class Grover:
+ """This class provides a framework for Grover Search algorithm [1].
+
+ Parameters
+ in_operator : callable ``f(qubits)``\n
+ Operator/Circuit of the initial search state for the algorithm, default Hadamards.
+ flip_operator : callable ``f(qubits)``\n
+ Operator/Circuit of marking the good states by phase-flip. Default doing a pauli-Z
+ gate at the last qubit.
+ zero_flip : callable ``f(qubits)``\n
+ Operator/Circuit of reflects 0s by phase-flip. Default doing a zero-controled pauli-Z
+ gate on qubits.
+ mark_data : ``str``, ``list[str]``\n
+ Marked target state. Default None.
+ Only used when simply marking a known query state, as the designed flip_operator part.
+ amplify_operator : callable ``f(qubits)``\n
+ Constructed complete Grover amplitude amplification operator circuit. Default None.
+ For users' special designed amplitude amplification operator.
+
+ References
+ [1] L. K. Grover, A fast quantum mechanical algorithm for database search. Proceedings
+ 28th Annual Symposium on the Theory of Computing (STOC) 1996, pp. 212-219.
+ https://arxiv.org/abs/quant-ph/9605043
+
+ """
+ def __init__(self,
+ in_operator=None,
+ flip_operator=None,
+ zero_flip=None,
+ mark_data=None,
+ amplify_operator=None):
+ self.in_operator = hadamard_circuit if in_operator is None else in_operator
+
+ if flip_operator is None and mark_data is not None:
+ def s_f(q_num):
+ return mark_data_reflection(q_num, mark_data)
+ self.flip_operator = s_f
+ else:
+ self.flip_operator = flip_operator
+ self.u_s = zero_flip
+ self.amplify = amplify_operator
+
+ def cir(self, q_input=None, q_flip=None, q_zero=None, iternum: int = 1):
+ """
+ Get full circuit of Grover search.
+
+ Parameters
+ q_input : ``QVec``\n
+ Target qubit(s) for in_operator (initial preparation circuit).
+ Using Hadamard gates to create the uniform superposition at the beginning most of time.
+ Although in most simple cases it includes the full workspace qubits,
+ auxiliary qubits can be excluded when dealing with some complex problems.
+ q_flip : ``QVec``\n
+ Target qubit(s) for flip_operator.
+ q_zero : ``QVec``\n
+ Target qubit(s) for zero_flip.
+ iternum : ``int``\n
+ The number of iterations. In another word number of repetition of applying the Grover operator.
+
+ Returns
+ circuit : ``QCircuit``\n
+ Full quantum circuit for given Grover search.
+
+ Examples
+ An example for implementing an Grover search for state where q_0 `and` q_1 is 1.
+
+ >>> from pyqpanda3.core import CPUQVM, QCircuit, Z, TOFFOLI
+ >>> from pyqpanda_alg import Grover
+ >>> m = CPUQVM()
+ >>> q_state = list(range(3))
+
+ >>> def mark(qubits):
+ >>> cir = QCircuit()
+ >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ >>> cir << Z(qubits[2])
+ >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ >>> return cir
+
+ >>> demo_search = Grover.Grover(flip_operator=mark)
+ >>> prog = QProg()
+ >>> prog << demo_search.cir(q_input=q_state[:2], q_flip=q_state, q_zero=q_state[:2], iternum=1)
+ >>> m.run(prog,1000)
+ >>> res = m.result().get_prob_dict(q_state[:2])
+ >>> print(res)
+ >>> print(prog)
+ {'00': 0.0, '01': 0.0, '10': 0.0, '11': 1.0000000000000004}
+
+ .. parsed-literal::
+ ┌─┐ ┌─┐ ─┐ ┌─┐ ┌─┐
+ q_0: |0>─┤H├ ─■─ ─── ─■─ ┤H├ ┤X├ ─■─ X├ ┤H├
+ ├─┤ │ │ ├─┤ ├─┤ ┌┴┐ ├─┤ ├─┤
+ q_1: |0>─┤H├ ■─ ─── ─■─ ┤H├ ┤X├ ┤Z ┤X├ ┤H├
+ └─┘ ┌┴┐ ┌─┐ ┌┴┐ └─┘ └─┘ └─┘ └─┘ └─
+ q_2: |0>──── ┤X├ ┤Z├ ┤X├ ─── ─── ─── ─── ───
+ └─┘ └─┘ └─┘
+
+ """
+ if iternum < 0:
+ raise ValueError(f"iternum must be non-negative, got {iternum}")
+
+ q_input = _ensure_list(q_input)
+ q_flip = _ensure_list(q_flip) if q_flip is not None else q_input
+ q_zero = _ensure_list(q_zero) if q_zero is not None else q_input
+
+ q_all = list(set(q_input + q_zero + q_flip))
+ if self.amplify is not None:
+ amp_cir = self.amplify(q_all)
+ else:
+ amp_cir = amp_operator(q_input, q_flip, q_zero,
+ self.in_operator, self.flip_operator, self.u_s)
+
+ circuit = QCircuit()
+ circuit << self.in_operator(q_input)
+ for _ in range(iternum):
+ circuit << amp_cir
+ return circuit
+
+ def search(self, q_input=None, q_flip=None, q_zero=None, iternum: int = 1, shots: int = 1000):
+ """
+ Run Grover search end-to-end: build circuit, execute, and return measurement results.
+
+ Parameters
+ q_input : ``QVec``\n
+ Target qubit(s) for in_operator.
+ q_flip : ``QVec``\n
+ Target qubit(s) for flip_operator.
+ q_zero : ``QVec``\n
+ Target qubit(s) for zero_flip.
+ iternum : ``int``\n
+ Number of Grover iterations.
+ shots : ``int``\n
+ Number of measurement shots. Default 1000.
+
+ Returns
+ result : ``dict``\n
+ Measurement probability dictionary.
+
+ Examples
+ >>> from pyqpanda_alg import Grover
+ >>> from pyqpanda3.core import TOFFOLI, Z, QCircuit
+ >>> def mark(qubits):
+ ... cir = QCircuit()
+ ... cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ ... cir << Z(qubits[2])
+ ... cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ ... return cir
+ >>> g = Grover.Grover(flip_operator=mark)
+ >>> q = list(range(3))
+ >>> result = g.search(q_input=q[:2], q_flip=q, q_zero=q[:2], iternum=1, shots=1000)
+ >>> print(result)
+ {'00': 0.0, '01': 0.0, '10': 0.0, '11': 1.0}
+ """
+ q_input = _ensure_list(q_input)
+ q_zero = _ensure_list(q_zero) if q_zero is not None else q_input
+
+ circuit = self.cir(q_input=q_input, q_flip=q_flip, q_zero=q_zero, iternum=iternum)
+
+ machine = CPUQVM()
+ prog = QProg()
+ prog << circuit
+ machine.run(prog, shots=shots)
+ return machine.result().get_prob_dict(q_input)
+
+
+def iter_num(q_num, sol_num):
+ """
+ Calculate the optimal number of iterations in Grover search.
+
+ Parameters
+ q_num : ``int``\n
+ The number of qubits in the search space. Search space size: :math:`N = 2 ^ {\\text {q_num}}`.
+ sol_num : ``int``\n
+ Number of target solution states.
+
+ Returns
+ num : The optimal number of iterations in Grover search.
+
+ Examples
+ An example for the case we show in the Grover search circuit. And we know there
+ is only one solution to be found. And total 2 qubits for the search space.
+
+ >>> from pyqpanda3.core import CPUQVM, QCircuit, Z, TOFFOLI
+ >>> from pyqpanda_alg import Grover
+ >>> m = CPUQVM()
+ >>> q_state = list(range(3))
+
+ >>> def mark(qubits):
+ >>> cir = QCircuit()
+ >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ >>> cir << Z(qubits[2])
+ >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ >>> return cir
+ >>> demo_search = Grover.Grover(flip_operator=mark)
+ >>> iter_num = Grover.iter_num(q_num=len(q_state), sol_num=2)
+ >>> print('best iter num: ', iter_num)
+ best iter num: 1
+
+ """
+ num = int(np.floor(np.pi * np.sqrt(2 ** q_num / sol_num) / 4))
+ return num
+
+
+def iter_analysis(q_num, sol_num, iternum=1):
+ """
+ Calculate the amplification probability and rotation angle for given amplitude
+ amplification iteration number.
+
+ Parameters
+ q_num : ``int``\n
+ The number of qubits in the search space. Search space size: :math:`N = 2 ^ {\\text {q_num}}`.
+ sol_num : ``int``\n
+ Number of target solution states.
+ iternum : ``int``\n
+ Given number of iteration.
+
+ Returns
+ prob, theta : (``float``, ``float``)\n
+ The amplification probability and rotation angle for given iteration.
+
+ Examples
+ An example for the case we show in the Grover search circuit. And we know there
+ is only one solution to be found. And total 2 qubits for the search space.
+
+ >>> from pyqpanda3.core import CPUQVM, QCircuit, Z, TOFFOLI
+ >>> from pyqpanda_alg import Grover
+ >>> m = CPUQVM()
+ >>> q_state = list(range(3))
+
+ >>> def mark(qubits):
+ >>> cir = QCircuit()
+ >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ >>> cir << Z(qubits[2])
+ >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ >>> return cir
+ >>> demo_search = Grover.Grover(flip_operator=mark)
+ >>> prob, angle = Grover.iter_analysis(q_num=len(q_state), sol_num=2, iternum=1)
+ >>> print('prob for getting one of the solution with given iter num 1:', prob)
+ >>> prob, angle = Grover.iter_analysis(q_num=len(q_state), sol_num=2, iternum=2)
+ >>> print('prob for getting one of the solution with given iter num 2:', prob)
+ prob for getting one of the solution with given iter num 1: 1.0
+ prob for getting one of the solution with given iter num 2: 0.24999999999999956
+
+ """
+ sol_prob = sol_num / (2 ** q_num)
+ theta = np.arcsin(np.sqrt(sol_prob)) * 2
+ prob = np.sin(((2 * iternum + 1) / 2) * theta) ** 2
+ return prob, theta
+
+
+def amp_operator(q_input=None, q_flip=None, q_zero=None, in_operator=None, flip_operator=None, zero_flip=None):
+ """
+ Construct complete Grover amplitude amplification operator.
+ Can be part of Grover/Quantum Count/QAE and other amplitude amplification related algorithm.
+
+ Parameters
+ q_input : ``QVec``\n
+ Target qubit(s) for in_operator (initial preparation circuit).
+ Using Hadamard gates to create the uniform superposition at the beginning most of time.
+ Although in most simple cases it includes the full workspace qubits,
+ auxiliary qubits can be excluded when dealing with some complex problems.
+ q_flip : ``QVec``\n
+ Target qubit(s) for flip_operator.
+ q_zero : ``QVec``\n
+ Target qubit(s) for zero_flip.
+ in_operator : callable ``f(qubits)``\n
+ Operator/Circuit of the initial search state for the algorithm, default Hadamards.
+ flip_operator : callable ``f(qubits)``\n
+ Operator/Circuit of marking the good states by phase-flip. Default doing a pauli-Z
+ gate at the last qubit.
+ zero_flip : callable ``f(qubits)``\n
+ Operator/Circuit of reflects 0s by phase-flip. Default doing a zero-controled pauli-Z
+ gate on qubits.
+
+ Returns
+ circuit : QCircuit\n
+ Amplitude amplification operator.
+
+ Examples
+ An example for constucting a amplitude amplification operator used in the case we show
+ in the Grover search circuit.
+
+ >>> from pyqpanda3.core import CPUQVM, QCircuit, Z, TOFFOLI
+ >>> from pyqpanda_alg import Grover
+ >>> m = CPUQVM()
+ >>> q_state = list(range(3))
+
+ >>> def mark(qubits):
+ >>> cir = QCircuit()
+ >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ >>> cir << Z(qubits[2])
+ >>> cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ >>> return cir
+ >>> print(Grover.amp_operator(q_input=q_state[:2], q_flip=q_state, q_zero=q_state[:2], flip_operator=mark))
+
+ .. parsed-literal::
+ ┌─┐ ┌─┐ ┌─┐ ┌─┐
+ q_0: |0>──■─ ── ─■─ ┤H├ X├ ─■─ ┤X├ ┤H├
+ │ │ ├─┤ ├─┤ ┌┐ ├─┤ ├─┤
+ q_1: |0>──■─ ─── ─■─ ┤H├ ┤X├ ┤Z├ X├ ┤H├
+ ┴┐ ┌─┐ ┌┴┐ └─┘ └─┘ └─┘ └─ └─┘
+ q_2: |0>─┤X├ Z├ ┤X├ ── ─── ─── ─── ───
+ └─┘ └─┘ └─┘
+
+ """
+ q_input = _ensure_list(q_input)
+ q_flip = _ensure_list(q_flip) if q_flip is not None else [q_input[-1]]
+ q_zero = _ensure_list(q_zero) if q_zero is not None else q_input
+
+ in_operator_cir = in_operator(q_input) if in_operator is not None else apply_QGate(q_input, H)
+
+ flip_operator = flip_operator(q_flip) if flip_operator is not None else Z(q_flip[-1])
+
+ if zero_flip is not None:
+ zero_flip = zero_flip(q_zero)
+ elif len(q_zero) == 1:
+ zero_flip = QCircuit()
+ zero_flip << X(q_zero[0]) << Z(q_zero[0]) << X(q_zero[0])
+ else:
+ zero_flip = QCircuit()
+ zero_flip << apply_QGate(q_zero, X)
+ zero_flip << apply_QGate([q_zero[-1]], Z).control(q_zero[:-1])
+ zero_flip << apply_QGate(q_zero, X)
+
+ circuit = QCircuit()
+ circuit << flip_operator
+ circuit << in_operator_cir.dagger() << zero_flip << in_operator_cir
+
+ return circuit
+
+
+def mark_data_reflection(qubits: list = None, mark_data=None):
+ """
+ Can be used to construct a phase flip operator for given target states.
+
+ Parameters
+ qubits : ``QVec``\n
+ Target qubit(s) for flip_operator.
+ mark_data : ``str``, ``list[str]``\n
+ Marked target state(s).
+
+ Returns
+ flip_operator : ``QCircuit``\n
+ A phase flip operator for given target states
+
+ Examples
+ An example for searching '101' and '001' using the flip operator given by this function.
+
+ >>> from pyqpanda3.core import CPUQVM, QProg
+ >>> from pyqpanda_alg import Grover
+ >>> m = CPUQVM()
+
+ >>> q_state = list(range(3))
+ >>> def mark(qubits):
+ >>> return Grover.mark_data_reflection(qubits=qubits, mark_data=['101', '001'])
+ >>> demo_search = Grover.Grover(flip_operator=mark)
+ >>> prog = QProg()
+ >>> prog << demo_search.cir(q_input=q_state)
+ >>> m.run(prog,1000)
+ >>> res = m.result().get_prob_dict(q_state)
+ >>> print(res)
+ {'000': 0.0, '001': 0.5000000000000002, '010': 0.0, '011': 0.0, '100': 0.0, '101': 0.5000000000000002, '110': 0.0, '111': 0.0}
+
+ """
+ qubits = _ensure_list(qubits)
+ flip_operator = QCircuit()
+
+ if isinstance(mark_data, str):
+ mark_data = [mark_data]
+ n = len(qubits)
+
+ for i in mark_data:
+ for j in range(n):
+ if i[-(j + 1)] == '0':
+ flip_operator << X(qubits[j])
+
+ flip_operator << Z(qubits[-1]).control(qubits[:-1])
+
+ for j in range(n):
+ if i[-(j + 1)] == '0':
+ flip_operator << X(qubits[j])
+
+ return flip_operator
+
+
+class GroverAdaptiveSearch:
+ """This class provides a framework for Grover Adaptive Search [2].
+
+ Parameters
+ init_value : ``float``\n
+ The given initial value of the optimization function.
+ n_index : ``int``\n
+ The number of qubits in the search space. Search space size: N = 2 ** q_num.
+ init_circuit : callable ``f(qubits)``\n
+ Operator/Circuit of the initial search state for the algorithm, default Hadamards.
+ oracle_circuit : callable ``f(qubits, value)``\n
+ Operator/Circuit of marking the `better` states by phase-flip. Default doing a pauli-Z
+ gate at the last qubit.
+
+ References
+ [2] A. Gilliam, S. Woerner, C. Gonciulea, Grover Adaptive Search for Constrained
+ Polynomial Binary Optimization. https://arxiv.org/abs/1912.04088
+
+ >>> from pyqpanda_alg import Grover
+ >>> import numpy as np
+ >>> from pyqpanda3.core import QCircuit, H, U1, SWAP
+
+ >>> def qft(qbs):
+ >>> n = len(qbs)
+ >>> qbs = qbs[::-1]
+ >>> cir = QCircuit()
+ >>> for j in range(1, n):
+ >>> cir << H(qbs[j - 1])
+ >>> for i in range(j, n):
+ >>> cir << U1(qbs[j - 1], np.pi * 2 ** (j - 1 - i)).control(qbs[i])
+ >>> cir << H(qbs[-1])
+ >>> for x in range(int(len(qbs)/2)):
+ >>> cir << SWAP(qbs[x], qbs[-x-1])
+ >>> return cir
+
+ >>> # flip if x0 * x1 + x0 - x1 - current_min < 0
+ >>> def flip_oracle_function(q_index_value, current_min):
+ >>> q_index = q_index_value[:2]
+ >>> q_value = q_index_value[2:]
+ >>> n_value = len(q_value)
+ >>> factor = np.pi * 2 ** (1 - n_value)
+ >>> cal_cir = QCircuit()
+ >>> for i, q_i in enumerate(q_value):
+ >>> cal_cir << H(q_i)
+ >>> cal_cir << U1(q_i, factor * 2 ** i).control(q_index)
+ >>> cal_cir << U1(q_i, factor * 2 ** i).control(q_index[0])
+ >>> cal_cir << U1(q_i, -factor * 2 ** i).control(q_index[1])
+ >>> cal_cir << U1(q_i, factor * 2 ** i * (-current_min))
+ >>> cal_cir << qft(q_value).dagger()
+ >>> return cal_cir
+
+ >>> demo_search = Grover.GroverAdaptiveSearch(init_value=0, n_index=2, oracle_circuit=flip_oracle_function)
+
+ >>> def n_value_function(current_min):
+ >>> n_value = 2 if current_min == 0 else 3
+ >>> return n_value
+
+ >>> def value_function(var_array):
+ >>> var_array = list(map(int, var_array))[::-1]
+ >>> value = var_array[0] * var_array[1] + var_array[0] - var_array[1]
+ >>> return value
+
+ >>> res = demo_search.run(continue_times=3,
+ >>> n_value_function=n_value_function,
+ >>> value_function=value_function,
+ >>> process_show=True)
+ >>> print(res)
+ ======searching 1 ,rotation = 1 ======
+ minimum Key Again: 00
+ minimum Value No Change: 0
+ ======searching 2 ,rotation = 1 ======
+ Current minimum Key: 10
+ Current minimum Value: -1
+ ======searching 1 ,rotation = 1 ======
+ minimum Key Again: 10
+ minimum Value No Change: -1
+ ======searching 2 ,rotation = 1 ======
+ ======searching 3 ,rotation = 1 ======
+ rotations: 5
+ ([[0, 1]], -1)
+
+ """
+ def __init__(self, init_value, n_index, init_circuit=None, oracle_circuit=None):
+ self._current_min = init_value
+ self.n_index = n_index
+ self.n_value = None
+ self.init_circuit = init_circuit
+ self.oracle_circuit = oracle_circuit
+
+ def _init_circuit(self, qlist):
+ if self.init_circuit is None:
+ return hadamard_circuit(qlist[:self.n_index])
+ else:
+ return self.init_circuit(qlist, self._current_min)
+
+ def _oracle_circuit(self, qlist):
+ if self.oracle_circuit is None:
+ return Z(qlist[-1])
+ else:
+ return self.oracle_circuit(qlist, self._current_min)
+
+ def run(self, continue_times: int = 3, n_value_function=None, value_function=None,
+ rotation_change='random', process_show=False):
+ """
+ Run the Grover Adaptive Search algorithm to find the minimum.
+
+ Parameters
+ continue_times : ``int``\n
+ The maximum number of repeated searches at the current optimal point.
+ n_value_function : callable ``f(value)``\n
+ Function for computing the number of qubits for marking the `better` states at current
+ best value, variable qubits not included.
+ value_function : callable ``f(var_array)``\n
+ Function for computing the problem value of given varriables array(str given as qpanda state).
+ rotation_change : ``str{'random', 'increase'}``, optional\n
+ The method to get the number of Grover iterations for each search of a search cycle.
+
+ - ``random`` : The number of Grover iterations for each search is randomly obtained from a
+ increasing interval. (Default)
+ - ``increase`` : The number of Grover iterations for each search is increasing.
+ process_show : ``bool``\n
+ Set to True to print the detail during search.
+
+ Returns
+ minimum_indexes, minimum_res : ( ``list[list[int]]``, ``float``)\n
+ The optimization result including the solution array and the optimal value.
+
+ Examples
+ An example for minimization of quadratic binary function: x0 * x1 + x0 - x1.
+
+
+ """
+ binary = True
+ num_all_solutions = 2 ** self.n_index
+ machine = CPUQVM()
+
+ minimum_found = False
+ minimum_res = self._current_min
+ minimum_indexes = []
+ indexes_measured = []
+
+ rotations = 0
+
+ while not minimum_found:
+ m = 1
+ improvement_found = False
+ loops_with_no_improvement = 0
+ while not improvement_found:
+ rotations += 1
+ self.n_value = n_value_function(self._current_min)
+
+ q_index_value = QProg(self.n_index + self.n_value).qubits()
+
+ if rotation_change == 'random':
+ rotation_count = 1 if m < 2 else np.random.randint(low=1, high=m)
+ elif rotation_change == 'increase':
+ rotation_count = int(m)
+ else:
+ raise NameError("Method not recognized")
+
+ if process_show:
+ print('======searching', loops_with_no_improvement + 1, ',rotation =', rotation_count, '======')
+ Grover_instance = Grover(in_operator=self._init_circuit, flip_operator=self._oracle_circuit)
+
+ zxy = Grover_instance.cir(q_input=q_index_value, q_flip=q_index_value, q_zero=q_index_value[:self.n_index],
+ iternum=rotation_count)
+
+ prog = QProg()
+ prog << zxy << measure_all(q_index_value[:self.n_index], q_index_value[:self.n_index])
+ machine.run(prog, shots=1)
+ prog_res = machine.result().get_prob_dict()
+ outcome = list(prog_res.keys())[0]
+
+ current_value = value_function(outcome)
+ v = current_value - self._current_min
+
+ if v < 0:
+ indexes_measured.append(outcome)
+ minimum_res = current_value
+ minimum_indexes = [outcome]
+ if process_show:
+ print('Current minimum Key: ', outcome)
+ print('Current minimum Value: ', minimum_res)
+ improvement_found = True
+ self._current_min = minimum_res
+
+
+ else:
+ loops_with_no_improvement += 1
+ if outcome not in indexes_measured:
+ indexes_measured.append(outcome)
+ if v == 0:
+ if outcome not in minimum_indexes:
+ minimum_indexes.append(outcome)
+ if process_show:
+ print('minimum Key Again: ', outcome)
+ print('minimum Value No Change: ', minimum_res)
+ m = min(m * 1.34, 2 ** (self.n_index / 2))
+ if loops_with_no_improvement >= continue_times or \
+ len(indexes_measured) == num_all_solutions:
+ improvement_found = True
+ minimum_found = True
+
+ minimum_indexes = list(set(minimum_indexes))
+ if binary:
+ opt_xs = []
+ for key in minimum_indexes:
+ opt_x = np.array([1 if s == '1' else 0 for s in ('{0:%s}' % self.n_index).format(key)])
+ opt_xs.append(opt_x.tolist()[::-1])
+ minimum_indexes = opt_xs
+ if process_show:
+ print('rotations: ', rotations)
+ return minimum_indexes, minimum_res
+
+ @staticmethod
+ def _bin_to_int(bin_value):
+ n_value = len(bin_value)
+ if bin_value.startswith("1"):
+ int_v = int(bin_value, 2) - 2 ** n_value
+ else:
+ int_v = int(bin_value, 2)
+ return int_v
diff --git a/pyqpanda-algorithm/pyqpanda_alg/Grover/__init__.py b/pyqpanda-algorithm/pyqpanda_alg/Grover/__init__.py
index d7eaf56..9f2116b 100644
--- a/pyqpanda-algorithm/pyqpanda_alg/Grover/__init__.py
+++ b/pyqpanda-algorithm/pyqpanda_alg/Grover/__init__.py
@@ -1,5 +1,6 @@
'''
-The QFinance module provides tools related to comparator, Quantum amplitude estimation, Grover algorithm, Grover optimization algorithm and QUBO problem solver, which are used to solve problems such as option pricing and portfolio optimization.
+The Grover module provides tools related to Grover search algorithm,
+amplitude amplification, and Grover Adaptive Search for combinatorial optimization.
'''
from .Grover_core import Grover,amp_operator,GroverAdaptiveSearch,mark_data_reflection,iter_num,iter_analysis
diff --git a/pyqpanda-algorithm/test/Grover/Test_Grover.py b/pyqpanda-algorithm/test/Grover/Test_Grover.py
new file mode 100644
index 0000000..a1dc214
--- /dev/null
+++ b/pyqpanda-algorithm/test/Grover/Test_Grover.py
@@ -0,0 +1,295 @@
+# 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 pytest
+import numpy as np
+
+from pyqpanda3.core import CPUQVM, QCircuit, QProg, TOFFOLI, Z
+from pyqpanda_alg.Grover import (
+ Grover,
+ GroverAdaptiveSearch,
+ amp_operator,
+ mark_data_reflection,
+ iter_num,
+ iter_analysis,
+)
+
+
+def mark_11(qubits):
+ """Oracle: mark |11> state using Toffoli + Z."""
+ cir = QCircuit()
+ cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ cir << Z(qubits[2])
+ cir << TOFFOLI(qubits[0], qubits[1], qubits[2])
+ return cir
+
+
+class TestGroverCir:
+ """Tests for Grover.cir() method."""
+
+ def setup_method(self):
+ self.qvm = CPUQVM()
+
+ def test_cir_basic(self):
+ """Basic circuit construction with TOFFOLI oracle."""
+ q_state = QProg(3).qubits()
+ g = Grover(flip_operator=mark_11)
+ circuit = g.cir(q_input=q_state[:2], q_flip=q_state, q_zero=q_state[:2], iternum=1)
+ assert isinstance(circuit, QCircuit)
+ assert circuit is not None
+
+ def test_cir_zero_iterations(self):
+ """Zero iterations should return only the initial Hadamard layer."""
+ q_state = QProg(3).qubits()
+ g = Grover(flip_operator=mark_11)
+ circuit = g.cir(q_input=q_state[:2], q_flip=q_state, q_zero=q_state[:2], iternum=0)
+ assert isinstance(circuit, QCircuit)
+
+ def test_cir_multiple_iterations(self):
+ """Multiple iterations should produce a valid circuit."""
+ q_state = QProg(3).qubits()
+ g = Grover(flip_operator=mark_11)
+ for iters in [1, 2, 3]:
+ circuit = g.cir(q_input=q_state[:2], q_flip=q_state, q_zero=q_state[:2], iternum=iters)
+ assert isinstance(circuit, QCircuit)
+ assert len(str(circuit)) > 0
+
+ def test_cir_negative_iterations_raises(self):
+ """Negative iteration count should raise ValueError."""
+ q_state = QProg(3).qubits()
+ g = Grover(flip_operator=mark_11)
+ with pytest.raises(ValueError, match="non-negative"):
+ g.cir(q_input=q_state[:2], q_flip=q_state, q_zero=q_state[:2], iternum=-1)
+
+ def test_cir_execution_finds_target(self):
+ """Grover search should find |11> with high probability."""
+ q_state = QProg(3).qubits()
+ g = Grover(flip_operator=mark_11)
+ circuit = g.cir(q_input=q_state[:2], q_flip=q_state, q_zero=q_state[:2], iternum=1)
+ prog = QProg()
+ prog << circuit
+ self.qvm.run(prog, shots=1000)
+ result = self.qvm.result().get_prob_dict(q_state[:2])
+ assert result['11'] >= 0.99
+
+ def test_cir_default_flip_operator(self):
+ """Default flip_operator (None) should use Z on last qubit."""
+ q_state = QProg(1).qubits()
+ g = Grover() # no flip_operator
+ circuit = g.cir(q_input=q_state, iternum=1)
+ assert isinstance(circuit, QCircuit)
+
+
+class TestGroverSearch:
+ """Tests for Grover.search() convenience method."""
+
+ def test_search_returns_dict(self):
+ """search() should return a probability dictionary."""
+ q = list(range(3))
+ g = Grover(flip_operator=mark_11)
+ result = g.search(q_input=q[:2], q_flip=q, q_zero=q[:2], iternum=1, shots=1000)
+ assert isinstance(result, dict)
+ assert '11' in result
+ assert result['11'] >= 0.99
+
+ def test_search_default_shots(self):
+ """search() should work with default shots parameter."""
+ q = list(range(3))
+ g = Grover(flip_operator=mark_11)
+ result = g.search(q_input=q[:2], q_flip=q, q_zero=q[:2], iternum=1)
+ assert isinstance(result, dict)
+
+
+class TestIterNum:
+ """Tests for iter_num() function."""
+
+ def test_single_solution(self):
+ """2 qubits, 1 solution -> optimal iterations = 1."""
+ assert iter_num(2, 1) == 1
+
+ def test_two_solutions(self):
+ """3 qubits, 2 solutions -> should return a positive integer."""
+ n = iter_num(3, 2)
+ assert isinstance(n, int)
+ assert n >= 1
+
+ def test_large_search_space(self):
+ """Larger search space should yield more iterations."""
+ assert iter_num(4, 1) >= iter_num(2, 1)
+
+
+class TestIterAnalysis:
+ """Tests for iter_analysis() function."""
+
+ def test_perfect_amplification(self):
+ """2 qubits, 1 solution, 1 iteration -> probability = 1.0."""
+ prob, theta = iter_analysis(2, 1, iternum=1)
+ assert abs(prob - 1.0) < 1e-10
+
+ def test_over_rotation(self):
+ """Too many iterations should reduce probability."""
+ prob1, _ = iter_analysis(2, 1, iternum=1)
+ prob2, _ = iter_analysis(2, 1, iternum=2)
+ assert prob2 < prob1
+
+ def test_returns_tuple(self):
+ """Should return (probability, angle) tuple."""
+ result = iter_analysis(3, 1, iternum=1)
+ assert len(result) == 2
+ assert isinstance(result[0], float)
+ assert isinstance(result[1], float)
+
+
+class TestAmpOperator:
+ """Tests for amp_operator() function."""
+
+ def test_amp_operator_returns_circuit(self):
+ """amp_operator should return a QCircuit."""
+ q = list(range(3))
+ circuit = amp_operator(
+ q_input=q[:2], q_flip=q, q_zero=q[:2],
+ flip_operator=mark_11
+ )
+ assert isinstance(circuit, QCircuit)
+
+ def test_amp_operator_default_flip(self):
+ """Default flip_operator should use Z on last qubit."""
+ q = [0]
+ circuit = amp_operator(q_input=q, q_flip=q, q_zero=q)
+ assert isinstance(circuit, QCircuit)
+
+ def test_amp_operator_single_qubit(self):
+ """Single qubit zero_flip should produce X-Z-X pattern."""
+ q = [0]
+ circuit = amp_operator(q_input=q, q_flip=q, q_zero=q)
+ assert isinstance(circuit, QCircuit)
+
+
+class TestMarkDataReflection:
+ """Tests for mark_data_reflection() function."""
+
+ def test_single_marked_state(self):
+ """Mark a single state string."""
+ q = [0, 1, 2]
+ circuit = mark_data_reflection(q, mark_data='101')
+ assert isinstance(circuit, QCircuit)
+
+ def test_multiple_marked_states(self):
+ """Mark multiple state strings."""
+ q = [0, 1, 2]
+ circuit = mark_data_reflection(q, mark_data=['101', '001'])
+ assert isinstance(circuit, QCircuit)
+
+ def test_mark_data_search_execution(self):
+ """Full Grover search with mark_data_reflection should find targets."""
+ q = list(range(3))
+
+ def mark(qubits):
+ return mark_data_reflection(qubits, mark_data=['101', '001'])
+
+ g = Grover(flip_operator=mark)
+ qvm = CPUQVM()
+ circuit = g.cir(q_input=q, q_flip=q, q_zero=q, iternum=1)
+ prog = QProg()
+ prog << circuit
+ qvm.run(prog, shots=1000)
+ result = qvm.result().get_prob_dict(q)
+ # Both marked states should have high probability
+ assert result.get('101', 0) + result.get('001', 0) >= 0.9
+
+
+class TestGroverAdaptiveSearch:
+ """Tests for GroverAdaptiveSearch class."""
+
+ def test_gas_basic_run(self):
+ """GAS should run without errors and return valid results."""
+ def flip_oracle(q_index_value, current_min):
+ from pyqpanda3.core import QCircuit, H, U1
+ from pyqpanda_alg.plugin import qft
+ q_index = q_index_value[:2]
+ q_value = q_index_value[2:]
+ n_value = len(q_value)
+ factor = np.pi * 2 ** (1 - n_value)
+ cal_cir = QCircuit()
+ for i, q_i in enumerate(q_value):
+ cal_cir << H(q_i)
+ cal_cir << U1(q_i, factor * 2 ** i).control(q_index)
+ cal_cir << U1(q_i, factor * 2 ** i).control(q_index[0])
+ cal_cir << U1(q_i, -factor * 2 ** i).control(q_index[1])
+ cal_cir << U1(q_i, factor * 2 ** i * (-current_min))
+ cal_cir << qft(q_value).dagger()
+ return cal_cir
+
+ def n_value_function(current_min):
+ return 2 if current_min == 0 else 3
+
+ def value_function(var_array):
+ var_array = list(map(int, var_array))[::-1]
+ return var_array[0] * var_array[1] + var_array[0] - var_array[1]
+
+ gas = GroverAdaptiveSearch(
+ init_value=0, n_index=2, oracle_circuit=flip_oracle
+ )
+ result = gas.run(
+ continue_times=3,
+ n_value_function=n_value_function,
+ value_function=value_function,
+ rotation_change='increase',
+ )
+ indexes, min_val = result
+ # GAS is probabilistic; the minimum should be <= 0 (the initial value)
+ assert min_val <= 0
+ assert isinstance(indexes, list)
+ assert len(indexes) >= 1
+
+ def test_gas_invalid_rotation_change(self):
+ """Invalid rotation_change should raise NameError."""
+ gas = GroverAdaptiveSearch(init_value=0, n_index=2)
+ with pytest.raises(NameError, match="not recognized"):
+ gas.run(
+ continue_times=1,
+ n_value_function=lambda _: 2,
+ value_function=lambda _: 0,
+ rotation_change='invalid',
+ )
+
+
+class TestGroverWithCustomAnsatz:
+ """Tests for Grover with custom in_operator and amplify_operator."""
+
+ def test_custom_in_operator(self):
+ """Custom initial state preparation should work."""
+ from pyqpanda3.core import X
+
+ def custom_init(qubits):
+ cir = QCircuit()
+ for q in qubits:
+ cir << X(q) # Start from |11> instead of uniform superposition
+ return cir
+
+ q = [0, 1]
+ g = Grover(in_operator=custom_init, flip_operator=lambda qbs: Z(qbs[-1]))
+ circuit = g.cir(q_input=q, iternum=1)
+ assert isinstance(circuit, QCircuit)
+
+ def test_custom_amplify_operator(self):
+ """Custom amplify_operator should be used instead of default."""
+ def custom_amp(q_all):
+ cir = QCircuit()
+ for q in q_all:
+ cir << Z(q)
+ return cir
+
+ q = [0, 1]
+ g = Grover(amplify_operator=custom_amp)
+ circuit = g.cir(q_input=q, iternum=1)
+ assert isinstance(circuit, QCircuit)