From 0d8b8cbf14040834755885f94b15d7b929c8f170 Mon Sep 17 00:00:00 2001 From: Manan Gupta Date: Sun, 9 Aug 2026 12:06:12 -0700 Subject: [PATCH] QSVD: skip the unused state-vector simulation in the loss inner loop --- pyqpanda-algorithm/pyqpanda_alg/QSVD/QSVD.py | 7 ++- test/QAlgBase/Test_QSVD_SVD.py | 59 ++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/pyqpanda-algorithm/pyqpanda_alg/QSVD/QSVD.py b/pyqpanda-algorithm/pyqpanda_alg/QSVD/QSVD.py index d43f868e..94961a6f 100644 --- a/pyqpanda-algorithm/pyqpanda_alg/QSVD/QSVD.py +++ b/pyqpanda-algorithm/pyqpanda_alg/QSVD/QSVD.py @@ -134,14 +134,15 @@ def loss(self, ls, return_type=True): machine.run(prog, 1000) re = machine.result().get_prob_list(qvec0 + qvec1) re = np.array(parse_quantum_result_list(re, qvec0+qvec1, select_max=-1)) - stv = StateVector(self.q0 + self.q1) - phase = stv.evolve(cir).ndarray().real - phase = phase.reshape(2**self.q1, 2**self.q0) prob = np.diagonal(re.reshape(2**self.q1, 2**self.q0)) same_p = np.sum(prob) if return_type: return 1-same_p else: + # state vector is only needed for the singular vectors, not for the loss + stv = StateVector(self.q0 + self.q1) + phase = stv.evolve(cir).ndarray().real + phase = phase.reshape(2**self.q1, 2**self.q0) return phase, np.argmax(abs(phase)) def QSVD_min(self): diff --git a/test/QAlgBase/Test_QSVD_SVD.py b/test/QAlgBase/Test_QSVD_SVD.py index 2d6ad838..91a3d698 100644 --- a/test/QAlgBase/Test_QSVD_SVD.py +++ b/test/QAlgBase/Test_QSVD_SVD.py @@ -1,6 +1,9 @@ +import time + import pytest import numpy as np from pyqpanda_alg.QSVD import SVD +from pyqpanda_alg.QSVD import QSVD as qsvd_module import warnings @@ -30,6 +33,62 @@ def test_orthogonality_properties(self): assert np.all(np.diff(q_sorted) <= 0), "QSVD奇异值应该降序排列" assert np.all(np.diff(np_sorted) <= 0), "NumPy奇异值应该降序排列" + def test_singular_values_against_numpy(self): + matrix = np.random.random(16).reshape([4, 4]) + + qsvd_instance = SVD(matrix_in=matrix) + para = qsvd_instance.QSVD_min() + q_singular = np.sort(np.diag(qsvd_instance.return_diag(para)))[::-1] + + np_singular = np.linalg.svd(matrix, compute_uv=False) + + rel_err = np.max(np.abs(q_singular - np_singular) / np_singular) + assert rel_err < 1e-2, f"singular value relative error too large: {rel_err}" + + def test_singular_vectors_against_numpy(self): + matrix = np.random.random(32).reshape([4, 8]) + + qsvd_instance = SVD(matrix_in=matrix) + para = qsvd_instance.QSVD_min() + # loss() reports the index the dominant singular value ended up on + max_index = qsvd_instance.loss(para, return_type=False)[1] + left = qsvd_instance.max_eig('0', para, max_index) + right = qsvd_instance.max_eig('1', para, max_index) + + u_np, s_np, v_np = np.linalg.svd(matrix) + + cos_left = abs(np.dot(left, u_np[:, 0])) / (np.linalg.norm(left) * np.linalg.norm(u_np[:, 0])) + cos_right = abs(np.dot(right, v_np[0])) / (np.linalg.norm(right) * np.linalg.norm(v_np[0])) + assert cos_left > 0.99, f"left singular vector overlap too low: {cos_left}" + assert cos_right > 0.99, f"right singular vector overlap too low: {cos_right}" + + def test_loss_skips_state_vector_on_scalar_path(self, monkeypatch): + # the scalar loss is the optimizer inner loop, it must not simulate the state vector + built = [] + real_state_vector = qsvd_module.StateVector + + def counting_state_vector(*args, **kwargs): + built.append(1) + return real_state_vector(*args, **kwargs) + + monkeypatch.setattr(qsvd_module, 'StateVector', counting_state_vector) + + qsvd_instance = SVD(matrix_in=np.random.random(16).reshape([4, 4])) + qsvd_instance.loss(qsvd_instance.parameter) + assert len(built) == 0, "scalar loss built a state vector" + + qsvd_instance.loss(qsvd_instance.parameter, return_type=False) + assert len(built) == 1, "matrix loss did not build a state vector" + + def test_qsvd_min_runtime(self): + matrix = np.random.random(64).reshape([8, 8]) + + start = time.perf_counter() + SVD(matrix_in=matrix).QSVD_min() + elapsed = time.perf_counter() - start + + assert elapsed < 20, f"QSVD_min on an 8x8 matrix took {elapsed:.1f}s" + if __name__ == "__main__": # 运行测试