From 356e11a4e765ba7787a8bf95afcdade32a83e362 Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Sun, 20 Sep 2026 18:52:52 -0400 Subject: [PATCH 1/7] feat: add quantum phase estimation --- pyqpanda-algorithm/pyqpanda_alg/QPE/qpe.py | 170 +++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 pyqpanda-algorithm/pyqpanda_alg/QPE/qpe.py diff --git a/pyqpanda-algorithm/pyqpanda_alg/QPE/qpe.py b/pyqpanda-algorithm/pyqpanda_alg/QPE/qpe.py new file mode 100644 index 00000000..8a618b44 --- /dev/null +++ b/pyqpanda-algorithm/pyqpanda_alg/QPE/qpe.py @@ -0,0 +1,170 @@ +# 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. + +"""Quantum Phase Estimation for PyQPanda3 circuits.""" + +from dataclasses import dataclass +from math import pi +from typing import Callable, Dict, Optional + +from pyqpanda3.core import CPUQVM, H, QCircuit, QProg + +from ..plugin import QFT + + +@dataclass(frozen=True) +class PhaseEstimationResult: + """Quantum phase estimation result.""" + + phase: float + bitstring: str + probability: float + resolution: float + probabilities: Dict[str, float] + + +def decode_phase(probabilities: Dict[str, float], precision_qubits: int) -> PhaseEstimationResult: + """Decode the most likely phase from a counting-register distribution.""" + if precision_qubits < 1: + raise ValueError("precision_qubits must be at least 1") + if not probabilities: + raise ValueError("probabilities cannot be empty") + + bitstring = max(probabilities, key=probabilities.get) + if len(bitstring) != precision_qubits or any(bit not in "01" for bit in bitstring): + raise ValueError( + f"expected {precision_qubits}-bit binary keys, got {bitstring!r}" + ) + + denominator = 1 << precision_qubits + return PhaseEstimationResult( + phase=int(bitstring, 2) / denominator, + bitstring=bitstring, + probability=float(probabilities[bitstring]), + resolution=1.0 / denominator, + probabilities=dict(probabilities), + ) + + +def phase_to_eigenvalue( + phase: float, + evolution_time: float = 1.0, + centered: bool = False, +) -> float: + """Convert a phase to an eigenvalue for U = exp(i * H * t).""" + if not 0.0 <= phase < 1.0: + raise ValueError("phase must lie in [0, 1)") + if evolution_time <= 0: + raise ValueError("evolution_time must be positive") + + wrapped = phase - 1.0 if centered and phase >= 0.5 else phase + return 2.0 * pi * wrapped / evolution_time + + +class QPE: + """Standard Quantum Phase Estimation. + + The unitary callable receives the system-qubit list and returns a QCircuit + implementing U. eigenstate_preparation may prepare an eigenstate before + phase kickback. + + For algorithms that can construct powers of U more efficiently than + repeating the base circuit, unitary_power(qubits, power) may be supplied + and must return a circuit implementing U raised to power. + """ + + def __init__( + self, + unitary: Callable[[list[int]], QCircuit], + system_qubits: int, + precision_qubits: int = 5, + eigenstate_preparation: Optional[Callable[[list[int]], QCircuit]] = None, + unitary_power: Optional[Callable[[list[int], int], QCircuit]] = None, + shots: int = 2048, + machine=None, + ) -> None: + if not callable(unitary): + raise TypeError("unitary must be callable") + if eigenstate_preparation is not None and not callable(eigenstate_preparation): + raise TypeError("eigenstate_preparation must be callable") + if unitary_power is not None and not callable(unitary_power): + raise TypeError("unitary_power must be callable") + if system_qubits < 1: + raise ValueError("system_qubits must be at least 1") + if precision_qubits < 1: + raise ValueError("precision_qubits must be at least 1") + if shots < 1: + raise ValueError("shots must be at least 1") + + self.unitary = unitary + self.system_qubits = system_qubits + self.precision_qubits = precision_qubits + self.eigenstate_preparation = eigenstate_preparation + self.unitary_power = unitary_power + self.shots = shots + self.machine = machine if machine is not None else CPUQVM() + + def _append_controlled_power( + self, + program: QProg, + system_register: list[int], + control_qubit: int, + power: int, + ) -> None: + if self.unitary_power is not None: + powered = self.unitary_power(system_register, power) + if not isinstance(powered, QCircuit): + raise TypeError("unitary_power must return QCircuit") + program << powered.control([control_qubit]) + return + + for _ in range(power): + base = self.unitary(system_register) + if not isinstance(base, QCircuit): + raise TypeError("unitary must return QCircuit") + program << base.control([control_qubit]) + + def build_program(self) -> tuple[QProg, list[int]]: + """Build QPE and return the program plus its counting register.""" + qubits = QProg(self.system_qubits + self.precision_qubits).qubits() + system_register = qubits[: self.system_qubits] + counting_register = qubits[self.system_qubits :] + + program = QProg() + if self.eigenstate_preparation is not None: + preparation = self.eigenstate_preparation(system_register) + if not isinstance(preparation, QCircuit): + raise TypeError("eigenstate_preparation must return QCircuit") + program << preparation + + for qubit in counting_register: + program << H(qubit) + + # Match the register-power convention already used by QAE: counting + # qubit i controls U^(2^i), followed by inverse QFT. + for index, control in enumerate(counting_register): + self._append_controlled_power( + program, + system_register, + control, + 1 << index, + ) + + program << QFT(counting_register).dagger() + return program, counting_register + + def run(self) -> PhaseEstimationResult: + """Execute QPE and return the dominant phase and full distribution.""" + program, counting_register = self.build_program() + self.machine.run(program, self.shots) + probabilities = self.machine.result().get_prob_dict(counting_register) + return decode_phase(probabilities, self.precision_qubits) From d76d03165d1ad572ec5af04c5d09e60c1d0fc284 Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Sun, 20 Sep 2026 18:52:54 -0400 Subject: [PATCH 2/7] feat: export QPE API --- pyqpanda-algorithm/pyqpanda_alg/QPE/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 pyqpanda-algorithm/pyqpanda_alg/QPE/__init__.py diff --git a/pyqpanda-algorithm/pyqpanda_alg/QPE/__init__.py b/pyqpanda-algorithm/pyqpanda_alg/QPE/__init__.py new file mode 100644 index 00000000..215818fb --- /dev/null +++ b/pyqpanda-algorithm/pyqpanda_alg/QPE/__init__.py @@ -0,0 +1,10 @@ +"""Quantum Phase Estimation public API.""" + +from .qpe import QPE, PhaseEstimationResult, decode_phase, phase_to_eigenvalue + +__all__ = [ + "QPE", + "PhaseEstimationResult", + "decode_phase", + "phase_to_eigenvalue", +] From a06f71b34db85b441fc926ad6caed502f2df5634 Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Sun, 20 Sep 2026 18:52:56 -0400 Subject: [PATCH 3/7] docs: add QPE example --- .../example/QAlgBase/testeg_QPE.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 pyqpanda-algorithm/example/QAlgBase/testeg_QPE.py diff --git a/pyqpanda-algorithm/example/QAlgBase/testeg_QPE.py b/pyqpanda-algorithm/example/QAlgBase/testeg_QPE.py new file mode 100644 index 00000000..56f808d5 --- /dev/null +++ b/pyqpanda-algorithm/example/QAlgBase/testeg_QPE.py @@ -0,0 +1,34 @@ +"""Minimal Quantum Phase Estimation example.""" + +from math import pi + +from pyqpanda3.core import QCircuit, U1, X + +from pyqpanda_alg.QPE import QPE + + +TARGET_PHASE = 3.0 / 8.0 + + +def prepare_one(qubits): + circuit = QCircuit() + circuit << X(qubits[0]) + return circuit + + +def phase_unitary(qubits): + circuit = QCircuit() + circuit << U1(qubits[0], 2.0 * pi * TARGET_PHASE) + return circuit + + +result = QPE( + unitary=phase_unitary, + system_qubits=1, + precision_qubits=5, + eigenstate_preparation=prepare_one, +).run() + +print("phase:", result.phase) +print("bitstring:", result.bitstring) +print("probability:", result.probability) From 91cc0ce68bb1ab9d6689799fc5473f72af024545 Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Sun, 20 Sep 2026 18:53:22 -0400 Subject: [PATCH 4/7] feat: expose QPE package --- pyqpanda-algorithm/pyqpanda_alg/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyqpanda-algorithm/pyqpanda_alg/__init__.py b/pyqpanda-algorithm/pyqpanda_alg/__init__.py index 12d68083..7b045bdb 100644 --- a/pyqpanda-algorithm/pyqpanda_alg/__init__.py +++ b/pyqpanda-algorithm/pyqpanda_alg/__init__.py @@ -46,6 +46,7 @@ from . import QUBO from . import QCmp from . import QAE +from . import QPE from . import QSVD from . import QSVR from . import Grover From c1c487fff97c12ccb3469787a71eae74a37f7b3b Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Sun, 20 Sep 2026 18:53:25 -0400 Subject: [PATCH 5/7] build: include QPE package initializer --- pyqpanda-algorithm/setup-cython.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyqpanda-algorithm/setup-cython.py b/pyqpanda-algorithm/setup-cython.py index 59246e21..c47fcce7 100644 --- a/pyqpanda-algorithm/setup-cython.py +++ b/pyqpanda-algorithm/setup-cython.py @@ -105,6 +105,7 @@ def run(self): self.copy_file(Path('pyqpanda_alg/HHL') / '__init__.py', root_dir, target_dir) self.copy_file(Path('pyqpanda_alg/QCPA') / '__init__.py', root_dir, target_dir) self.copy_file(Path('pyqpanda_alg/QCmp') / '__init__.py', root_dir, target_dir) + self.copy_file(Path('pyqpanda_alg/QPE') / '__init__.py', root_dir, target_dir) self.copy_file(Path('pyqpanda_alg/Grover') / '__init__.py', root_dir, target_dir) remove_all(target_dir) From f31c2aa6e4a1d038d69e606c01ed9312667a131f Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Sun, 20 Sep 2026 18:53:27 -0400 Subject: [PATCH 6/7] docs: document quantum phase estimation --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a12fbe0d..2ff4407c 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,9 @@ pyqpanda-algorithm 是由本源量子(Origin Quantum)开发的量子算法 - **QSVD(量子变分奇异值分解)** 在变分框架下提取矩阵的奇异值与奇异向量,用于降维与推荐系统。 +- **QPE(量子相位估计)** + 通过受控酉演化与逆量子傅里叶变换估计酉算符本征相位,并支持将相位换算为哈密顿量本征值。 + ### 4. **通用工具与基础组件** 提供量子振幅估计算法、比较器、稀疏编码等底层工具。 From f0bd921f579b2aa3f1cd38db9903921e64a31867 Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Sun, 20 Sep 2026 18:53:32 -0400 Subject: [PATCH 7/7] docs: add QPE to English overview --- README_EN.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README_EN.md b/README_EN.md index d74c953c..2c179d68 100644 --- a/README_EN.md +++ b/README_EN.md @@ -50,6 +50,8 @@ Integrates quantum computing into classical machine learning to improve efficien Solves key problems in physical modeling and engineering simulation (e.g., eigenvalues, linear equations, matrix decomposition). - **QSVD (Quantum Variational Singular Value Decomposition)** Extracts matrix singular values/vectors under a variational framework (for dimensionality reduction and recommendation systems). +- **QPE (Quantum Phase Estimation)** + Estimates eigenphases with controlled unitary evolution and inverse QFT, with helpers for converting phases to Hamiltonian eigenvalues. ### 4. General Tools & Basic Components Provides underlying tools for quantum computing workflows.