From cd099719c2a0682b1a921634d667714f605fad4b Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Mon, 16 Feb 2026 00:19:33 +0800 Subject: [PATCH 1/3] - Added `graphix.sim.statevec.Statevec.fidelity` and `graphix.sim.statevec.Statevec.isclose` methods to calculate and check fidelity between two states. - Updated old code with uses of the above methods. - Solves [#428](https://github.com/TeamGraphix/graphix/issues/428). --- CHANGELOG.md | 4 +++ graphix/sim/statevec.py | 41 +++++++++++++++++++++ tests/test_flow_core.py | 3 +- tests/test_graphsim.py | 22 ++++++------ tests/test_opengraph.py | 7 ++-- tests/test_optimization.py | 8 ++--- tests/test_parameter.py | 2 +- tests/test_pattern.py | 50 +++++++++++++------------- tests/test_pyzx.py | 7 ++-- tests/test_qasm3_exporter_to_qiskit.py | 2 +- tests/test_statevec.py | 34 ++++++++++++++++++ tests/test_statevec_backend.py | 2 +- tests/test_tnsim.py | 5 +-- tests/test_transpiler.py | 32 ++++++++--------- 14 files changed, 149 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 561f0a839..e7da48668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Fixed +- #435 + - Added `graphix.sim.statevec.Statevec.fidelity` and `graphix.sim.statevec.Statevec.isclose` methods to calculate and check fidelity between two states. + - Updated old code with uses of the above methods. + - Solves [#386](https://github.com/TeamGraphix/graphix/issues/386). - #429 - Modify `graphix.noise_models.noise_model.ApplyNoise` to handle conditionality based on a `domain` attribute (like `command.X` and `command.Z`). diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 259ca3d98..6385bb3f7 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -400,6 +400,47 @@ def xreplace(self, assignment: Mapping[Parameter, ExpressionOrSupportsFloat]) -> result.psi = np.vectorize(lambda value: parameter.xreplace(value, assignment))(self.psi) return result + def fidelity(self, other: Statevec) -> float: + r"""Return the fidelity with another statevector. + + This is calculated as + + .. math:: + F(\psi_{1}, \psi_{2}) = |\langle \psi_{1} | \psi_{2} \rangle|^2 + + Parameters + ---------- + other : :class:`graphix.sim.statevec.Statevec` + other statevector + + Returns + ------- + float : fidelity between self and other + """ + st1 = copy.copy(self) + st1.normalize() + st2 = copy.copy(other) + st2.normalize() + return float(np.abs(np.vdot(st1.psi.flatten(), st2.psi.flatten()))) ** 2 + + def isclose(self, other: Statevec, atol: float = 1e-15, rtol: float = 1e-10) -> bool: + r"""Return whether the state is equal to another state up to global phase. + + Parameters + ---------- + other : :class:`graphix.sim.statevec.Statevec` + other statevector + atol : float, optional + absolute tolerance for numerical comparison, defaults to 1e-15 + rtol : float, optional + relative tolerance for numerical comparison, defaults to 1e-10 + + Returns + ------- + bool : whether the states are equal up to global phase + """ + return math.isclose(self.fidelity(other), 1, abs_tol=atol, rel_tol=rtol) + @dataclass(frozen=True) class StatevectorBackend(DenseStateBackend[Statevec]): diff --git a/tests/test_flow_core.py b/tests/test_flow_core.py index 47894dd7f..e2d1555b5 100644 --- a/tests/test_flow_core.py +++ b/tests/test_flow_core.py @@ -412,8 +412,7 @@ def test_corrections_to_pattern(self, test_case: XZCorrectionsTestCase, fx_rng: for _ in range(n_shots): state = pattern.simulate_pattern(input_state=PlanarState(plane, alpha)) - result = np.abs(np.dot(state.flatten().conjugate(), state_ref.flatten())) - assert result == pytest.approx(1) + assert state.isclose(state_ref) class TestFlow: diff --git a/tests/test_graphsim.py b/tests/test_graphsim.py index 6e91f6702..4c247138f 100644 --- a/tests/test_graphsim.py +++ b/tests/test_graphsim.py @@ -87,21 +87,21 @@ def test_fig2(self) -> None: gstate.normalize() gstate.remove_qubit(0) gstate2 = graph_state_to_statevec(g) - assert np.abs(np.dot(gstate.flatten().conjugate(), gstate2.flatten())) == pytest.approx(1) + assert gstate.isclose(gstate2) g.measure_y(1, choice=0) gstate.evolve_single(meas_op(0.5 * ANGLE_PI), 0) # y meas gstate.normalize() gstate.remove_qubit(0) gstate2 = graph_state_to_statevec(g) - assert np.abs(np.dot(gstate.flatten().conjugate(), gstate2.flatten())) == pytest.approx(1) + assert gstate.isclose(gstate2) g.measure_z(3) gstate.evolve_single(meas_op(0.5 * ANGLE_PI, plane=Plane.YZ), 1) # z meas gstate.normalize() gstate.remove_qubit(1) gstate2 = graph_state_to_statevec(g) - assert np.abs(np.dot(gstate.flatten().conjugate(), gstate2.flatten())) == pytest.approx(1) + assert gstate.isclose(gstate2) def test_e2(self) -> None: nqubit = 6 @@ -112,23 +112,23 @@ def test_e2(self) -> None: g.equivalent_graph_e2(3, 4) gstate2 = graph_state_to_statevec(g) - assert np.abs(np.dot(gstate.flatten().conjugate(), gstate2.flatten())) == pytest.approx(1) + assert gstate.isclose(gstate2) g.equivalent_graph_e2(4, 0) gstate3 = graph_state_to_statevec(g) - assert np.abs(np.dot(gstate.flatten().conjugate(), gstate3.flatten())) == pytest.approx(1) + assert gstate.isclose(gstate3) g.equivalent_graph_e2(4, 5) gstate4 = graph_state_to_statevec(g) - assert np.abs(np.dot(gstate.flatten().conjugate(), gstate4.flatten())) == pytest.approx(1) + assert gstate.isclose(gstate4) g.equivalent_graph_e2(0, 3) gstate5 = graph_state_to_statevec(g) - assert np.abs(np.dot(gstate.flatten().conjugate(), gstate5.flatten())) == pytest.approx(1) + assert gstate.isclose(gstate5) g.equivalent_graph_e2(0, 3) gstate6 = graph_state_to_statevec(g) - assert np.abs(np.dot(gstate.flatten().conjugate(), gstate6.flatten())) == pytest.approx(1) + assert gstate.isclose(gstate6) def test_e1(self) -> None: nqubit = 6 @@ -139,15 +139,15 @@ def test_e1(self) -> None: g.equivalent_graph_e1(3) gstate2 = graph_state_to_statevec(g) - assert np.abs(np.dot(gstate.flatten().conjugate(), gstate2.flatten())) == pytest.approx(1) + assert gstate.isclose(gstate2) g.z(4) gstate = graph_state_to_statevec(g) g.equivalent_graph_e1(4) gstate2 = graph_state_to_statevec(g) - assert np.abs(np.dot(gstate.flatten().conjugate(), gstate2.flatten())) == pytest.approx(1) + assert gstate.isclose(gstate2) g.equivalent_graph_e1(4) gstate3 = graph_state_to_statevec(g) - assert np.abs(np.dot(gstate.flatten().conjugate(), gstate3.flatten())) == pytest.approx(1) + assert gstate.isclose(gstate3) def test_local_complement(self) -> None: nqubit = 6 diff --git a/tests/test_opengraph.py b/tests/test_opengraph.py index 4a2409059..cd830fadf 100644 --- a/tests/test_opengraph.py +++ b/tests/test_opengraph.py @@ -799,9 +799,8 @@ def check_determinism(pattern: Pattern, fx_rng: Generator, n_shots: int = 3) -> for _ in range(n_shots): state = pattern.simulate_pattern(input_state=PlanarState(plane, alpha)) - result = np.abs(np.dot(state.flatten().conjugate(), state_ref.flatten())) - if result == pytest.approx(1): + if state.isclose(state_ref): continue return False @@ -871,7 +870,7 @@ def test_double_entanglement(self) -> None: pattern2 = pattern.extract_opengraph().to_pattern() state = pattern.simulate_pattern() state2 = pattern2.simulate_pattern() - assert np.abs(np.dot(state.flatten().conjugate(), state2.flatten())) == pytest.approx(1) + assert state.isclose(state2) def test_from_to_pattern(self, fx_rng: Generator) -> None: n_qubits = 2 @@ -884,7 +883,7 @@ def test_from_to_pattern(self, fx_rng: Generator) -> None: alpha = 2 * ANGLE_PI * fx_rng.random() state_ref = pattern_ref.simulate_pattern(input_state=PlanarState(plane, alpha)) state = pattern.simulate_pattern(input_state=PlanarState(plane, alpha)) - assert np.abs(np.dot(state.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state.isclose(state_ref) def test_isclose_measurement(self) -> None: og_1 = OpenGraph( diff --git a/tests/test_optimization.py b/tests/test_optimization.py index d1375fdda..ca65405fe 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -49,7 +49,7 @@ def test_standardize_clifford_entanglement(fx_rng: Generator) -> None: state_ref = p_ref.simulate_pattern(input_state=PlanarState(Plane.XY, alpha)) state_p = p.simulate_pattern(input_state=PlanarState(Plane.XY, alpha)) - assert np.abs(np.dot(state_p.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state_ref.isclose(state_p) @pytest.mark.parametrize("jumps", range(1, 11)) @@ -66,7 +66,7 @@ def test_incorporate_pauli_results(fx_bg: PCG64, jumps: int) -> None: pattern2 = incorporate_pauli_results(pattern) state = pattern.simulate_pattern(rng=rng) state2 = pattern2.simulate_pattern(rng=rng) - assert np.abs(np.dot(state.flatten().conjugate(), state2.flatten())) == pytest.approx(1) + assert state.isclose(state2) @pytest.mark.parametrize("jumps", range(1, 11)) @@ -100,7 +100,7 @@ def test_remove_useless_domains(fx_bg: PCG64, jumps: int) -> None: pattern2 = remove_useless_domains(pattern) state = pattern.simulate_pattern(rng=rng) state2 = pattern2.simulate_pattern(rng=rng) - assert np.abs(np.dot(state.flatten().conjugate(), state2.flatten())) == pytest.approx(1) + assert state.isclose(state2) def test_to_space_optimal_pattern() -> None: @@ -122,4 +122,4 @@ def test_to_space_optimal_pattern() -> None: pattern2 = StandardizedPattern.from_pattern(pattern).to_space_optimal_pattern() state = pattern.simulate_pattern() state2 = pattern2.simulate_pattern() - assert np.abs(np.dot(state.flatten().conjugate(), state2.flatten())) == pytest.approx(1) + assert state.isclose(state2) diff --git a/tests/test_parameter.py b/tests/test_parameter.py index 8b701f2ce..67e18992b 100644 --- a/tests/test_parameter.py +++ b/tests/test_parameter.py @@ -168,7 +168,7 @@ def test_random_circuit_with_parameters(fx_bg: PCG64, jumps: int, use_xreplace: else: state = circuit.subs(alpha, assignment[alpha]).subs(beta, assignment[beta]).simulate_statevector().statevec state_mbqc = pattern.subs(alpha, assignment[alpha]).subs(beta, assignment[beta]).simulate_pattern() - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state.isclose(state_mbqc) def test_visualization() -> None: diff --git a/tests/test_pattern.py b/tests/test_pattern.py index b0bd65d35..8f6856ff3 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -74,7 +74,7 @@ def test_standardize(self, fx_rng: Generator) -> None: assert pattern.is_standard() state = circuit.simulate_statevector().statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state.isclose(state_mbqc) def test_minimize_space(self, fx_rng: Generator) -> None: nqubits = 5 @@ -85,7 +85,7 @@ def test_minimize_space(self, fx_rng: Generator) -> None: pattern.minimize_space() state = circuit.simulate_statevector().statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state.isclose(state_mbqc) # https://github.com/TeamGraphix/graphix/issues/157 @pytest.mark.parametrize( @@ -158,7 +158,7 @@ def test_minimize_space_with_gflow(self, fx_bg: PCG64, jumps: int) -> None: pattern.minimize_space() state = circuit.simulate_statevector().statevec state_mbqc = pattern.simulate_pattern(rng=rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state.isclose(state_mbqc) @pytest.mark.filterwarnings("ignore:Simulating using densitymatrix backend with no noise.") @pytest.mark.parametrize("backend_type", ["statevector", "densitymatrix", "tensornetwork"]) @@ -202,7 +202,7 @@ def test_parallelize_pattern(self, fx_rng: Generator) -> None: pattern.parallelize_pattern() state = circuit.simulate_statevector().statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state.isclose(state_mbqc) @pytest.mark.parametrize("jumps", range(1, 11)) def test_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: @@ -216,7 +216,7 @@ def test_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: assert pattern.is_standard() state = circuit.simulate_statevector().statevec state_mbqc = pattern.simulate_pattern(rng=rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) @pytest.mark.parametrize("jumps", range(1, 11)) @pytest.mark.parametrize("backend", ["statevector", "densitymatrix"]) @@ -267,7 +267,7 @@ def test_pauli_measurement_single(self, plane: Plane, angle: float) -> None: pattern.perform_pauli_measurements() state = pattern.simulate_pattern() state_ref = pattern_ref.simulate_pattern(branch_selector=ConstBranchSelector(0)) - assert np.abs(np.dot(state.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state.isclose(state_ref) def test_pauli_measurement(self) -> None: # test pattern is obtained from 3-qubit QFT with pauli measurement @@ -338,7 +338,7 @@ def test_pauli_measured_against_nonmeasured(self, fx_bg: PCG64, jumps: int, igno pattern1.perform_pauli_measurements(ignore_pauli_with_deps=ignore_pauli_with_deps) state = pattern.simulate_pattern(rng=rng) state1 = pattern1.simulate_pattern(rng=rng) - assert np.abs(np.dot(state.flatten().conjugate(), state1.flatten())) == pytest.approx(1) + assert state.isclose(state1) @pytest.mark.parametrize("jumps", range(1, 4)) def test_pauli_repeated_measurement(self, fx_bg: PCG64, jumps: int) -> None: @@ -429,7 +429,7 @@ def test_shift_signals_plane(self, plane: Plane, method: str) -> None: outcomes_p = shift_outcomes(outcomes_ref, signal_dict) branch_selector = FixedBranchSelector(results=outcomes_p) state_p = pattern.simulate_pattern(branch_selector=branch_selector) - assert np.abs(np.dot(state_p.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state_p.isclose(state_ref) @pytest.mark.parametrize("jumps", range(1, 11)) def test_standardize_direct(self, fx_bg: PCG64, jumps: int) -> None: @@ -443,7 +443,7 @@ def test_standardize_direct(self, fx_bg: PCG64, jumps: int) -> None: pattern.minimize_space() state_p = pattern.simulate_pattern() state_ref = circuit.simulate_statevector().statevec - assert np.abs(np.dot(state_p.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state_p.isclose(state_ref) @pytest.mark.parametrize("jumps", range(1, 11)) def test_shift_signals_direct(self, fx_bg: PCG64, jumps: int) -> None: @@ -457,7 +457,7 @@ def test_shift_signals_direct(self, fx_bg: PCG64, jumps: int) -> None: pattern.minimize_space() state_p = pattern.simulate_pattern() state_ref = circuit.simulate_statevector().statevec - assert np.abs(np.dot(state_p.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state_p.isclose(state_ref) @pytest.mark.parametrize("jumps", range(1, 11)) def test_pauli_measurement_then_standardize(self, fx_bg: PCG64, jumps: int) -> None: @@ -472,7 +472,7 @@ def test_pauli_measurement_then_standardize(self, fx_bg: PCG64, jumps: int) -> N pattern.minimize_space() state = circuit.simulate_statevector().statevec state_mbqc = pattern.simulate_pattern() - assert compare_backend_result_with_statevec(state_mbqc, state) == pytest.approx(1) + assert state_mbqc.isclose(state) @pytest.mark.parametrize("jumps", range(1, 11)) def test_standardize_two_cliffords(self, fx_bg: PCG64, jumps: int) -> None: @@ -485,7 +485,7 @@ def test_standardize_two_cliffords(self, fx_bg: PCG64, jumps: int) -> None: pattern.standardize() state_ref = pattern_ref.simulate_pattern() state_p = pattern.simulate_pattern() - assert np.abs(np.dot(state_p.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state_p.isclose(state_ref) @pytest.mark.parametrize("jumps", range(1, 48)) def test_standardize_domains_and_clifford(self, fx_bg: PCG64, jumps: int) -> None: @@ -502,7 +502,7 @@ def test_standardize_domains_and_clifford(self, fx_bg: PCG64, jumps: int) -> Non pattern.standardize() state_ref = pattern_ref.simulate_pattern() state_p = pattern.simulate_pattern() - assert np.abs(np.dot(state_p.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state_p.isclose(state_ref) # Simple pattern composition def test_compose_1(self) -> None: @@ -721,7 +721,7 @@ def test_compose_6(self, fx_bg: PCG64, jumps: int) -> None: p_compose.minimize_space() s = p.simulate_pattern() s_compose = p_compose.simulate_pattern() - assert np.abs(np.dot(s.flatten().conjugate(), s_compose.flatten())) == pytest.approx(1) + assert s.isclose(s_compose) # Test warning composition after standardization def test_compose_7(self, fx_rng: Generator) -> None: @@ -991,7 +991,7 @@ def test_extract_causal_flow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None s_ref = p_ref.simulate_pattern(rng=rng) s_test = p_test.simulate_pattern(rng=rng) - assert np.abs(np.dot(s_ref.flatten().conjugate(), s_test.flatten())) == pytest.approx(1) + assert s_ref.isclose(s_test) # Extract gflow from random circuits @pytest.mark.parametrize("jumps", range(1, 11)) @@ -1009,7 +1009,7 @@ def test_extract_gflow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: s_ref = p_ref.simulate_pattern(rng=rng) s_test = p_test.simulate_pattern(rng=rng) - assert np.abs(np.dot(s_ref.flatten().conjugate(), s_test.flatten())) == pytest.approx(1) + assert s_ref.isclose(s_test) @pytest.mark.parametrize("test_case", PATTERN_FLOW_TEST_CASES) def test_extract_causal_flow(self, fx_rng: Generator, test_case: PatternFlowTestCase) -> None: @@ -1020,7 +1020,7 @@ def test_extract_causal_flow(self, fx_rng: Generator, test_case: PatternFlowTest p_test = test_case.pattern.extract_causal_flow().to_corrections().to_pattern() s_test = p_test.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) - assert np.abs(np.dot(s_ref.flatten().conjugate(), s_test.flatten())) == pytest.approx(1) + assert s_ref.isclose(s_test) else: with pytest.raises(FlowError): test_case.pattern.extract_causal_flow() @@ -1034,7 +1034,7 @@ def test_extract_gflow(self, fx_rng: Generator, test_case: PatternFlowTestCase) p_test = test_case.pattern.extract_gflow().to_corrections().to_pattern() s_test = p_test.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) - assert np.abs(np.dot(s_ref.flatten().conjugate(), s_test.flatten())) == pytest.approx(1) + assert s_ref.isclose(s_test) else: with pytest.raises(FlowError): test_case.pattern.extract_gflow() @@ -1060,7 +1060,7 @@ def test_extract_cflow_og(self, fx_rng: Generator) -> None: p_test = p_ref.extract_causal_flow().to_corrections().to_pattern() s_test = p_test.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha)) - assert np.abs(np.dot(s_ref.flatten().conjugate(), s_test.flatten())) == pytest.approx(1) + assert s_ref.isclose(s_test) # From open graph def test_extract_gflow_og(self, fx_rng: Generator) -> None: @@ -1084,7 +1084,7 @@ def test_extract_gflow_og(self, fx_rng: Generator) -> None: p_test = p_ref.extract_gflow().to_corrections().to_pattern() s_test = p_test.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha)) - assert np.abs(np.dot(s_ref.flatten().conjugate(), s_test.flatten())) == pytest.approx(1) + assert s_ref.isclose(s_test) # Extract xz-corrections from random circuits @pytest.mark.parametrize("jumps", range(1, 11)) @@ -1104,7 +1104,7 @@ def test_extract_xzc_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: s_ref = p_ref.simulate_pattern(rng=rng) s_test = p_test.simulate_pattern(rng=rng) - assert np.abs(np.dot(s_ref.flatten().conjugate(), s_test.flatten())) == pytest.approx(1) + assert s_ref.isclose(s_test) def test_extract_xzc_empty_domains(self) -> None: p = Pattern(input_nodes=[0], cmds=[N(1), E((0, 1))]) @@ -1233,7 +1233,7 @@ def test_standardize(self, fx_bg: PCG64, jumps: int) -> None: pattern_mc.minimize_space() state_d = pattern.simulate_pattern(rng=rng) state_ref = pattern_mc.simulate_pattern(rng=rng) - assert np.abs(np.dot(state_d.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state_d.isclose(state_ref) @pytest.mark.parametrize("jumps", range(1, 11)) def test_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: @@ -1252,7 +1252,7 @@ def test_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: pattern_mc.minimize_space() state_d = pattern.simulate_pattern(rng=rng) state_ref = pattern_mc.simulate_pattern(rng=rng) - assert np.abs(np.dot(state_d.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state_d.isclose(state_ref) @pytest.mark.parametrize("jumps", range(1, 11)) def test_standardize_and_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: @@ -1267,7 +1267,7 @@ def test_standardize_and_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: pattern.minimize_space() state_p = pattern.simulate_pattern(rng=rng) state_ref = circuit.simulate_statevector().statevec - assert np.abs(np.dot(state_p.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state_p.isclose(state_ref) @pytest.mark.parametrize("jumps", range(1, 4)) def test_mixed_pattern_operations(self, fx_bg: PCG64, jumps: int) -> None: @@ -1296,7 +1296,7 @@ def test_mixed_pattern_operations(self, fx_bg: PCG64, jumps: int) -> None: assert pattern.is_standard() pattern.minimize_space() state_p = pattern.simulate_pattern(rng=rng) - assert np.abs(np.dot(state_p.flatten().conjugate(), state_ref.flatten())) == pytest.approx(1) + assert state_p.isclose(state_ref) def test_pauli_measurement_end_with_measure(self) -> None: # https://github.com/TeamGraphix/graphix/issues/153 diff --git a/tests/test_pyzx.py b/tests/test_pyzx.py index d77217da8..57aae7270 100644 --- a/tests/test_pyzx.py +++ b/tests/test_pyzx.py @@ -93,7 +93,7 @@ def test_random_circuit(fx_bg: PCG64, jumps: int) -> None: pattern2.perform_pauli_measurements() pattern2.minimize_space() state2 = pattern2.simulate_pattern() - assert np.abs(np.dot(state.flatten().conjugate(), state2.flatten())) == pytest.approx(1) + assert state.isclose(state2) def test_rz() -> None: @@ -107,7 +107,7 @@ def test_rz() -> None: pattern_zx = og.to_pattern() state = pattern.simulate_pattern() state_zx = pattern_zx.simulate_pattern() - assert np.abs(np.dot(state_zx.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_zx.isclose(state) # Issue #235 @@ -128,4 +128,5 @@ def test_full_reduce_toffoli() -> None: p2 = og2.to_pattern() s = p.simulate_pattern() s2 = p2.simulate_pattern() - print(np.abs(np.dot(s.flatten().conj(), s2.flatten()))) + print(s.fidelity(s2)) + assert s.isclose(s2) diff --git a/tests/test_qasm3_exporter_to_qiskit.py b/tests/test_qasm3_exporter_to_qiskit.py index cc16ba71c..7dcdbc6e3 100644 --- a/tests/test_qasm3_exporter_to_qiskit.py +++ b/tests/test_qasm3_exporter_to_qiskit.py @@ -77,7 +77,7 @@ def check_qasm3(pattern: Pattern) -> None: backend.finalize(pattern.output_nodes) state_qiskit = backend.state state_mbqc = pattern.simulate_pattern(branch_selector=branch_selector) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state_qiskit.flatten())) == pytest.approx(1) + assert state_qiskit.isclose(state_mbqc) def test_to_qasm3_qubits_preparation() -> None: diff --git a/tests/test_statevec.py b/tests/test_statevec.py index c76851957..e6aa70ca4 100644 --- a/tests/test_statevec.py +++ b/tests/test_statevec.py @@ -144,6 +144,40 @@ def test_copy_fail(self, fx_rng: Generator) -> None: with pytest.raises(ValueError): _vec = Statevec(nqubit=length - 1, data=test_vec) + def test_fidelity(self) -> None: + n = 3 + sv1 = Statevec(nqubit=n) + sv2 = Statevec(nqubit=n) + + # generate uniform random statevector + random_psi = np.random.uniform(-1, 1, 2**n) + 1j * np.random.uniform(-1, 1, 2**n) + random_psi /= np.linalg.norm(random_psi) + + sv1.psi = random_psi + sv2.psi = np.copy(sv1.psi) + # global phase should not affect the result + sv2.psi *= np.exp(1j * np.pi / 3) + + # We are unit testing :meth:`Statevec.fidelity`, so we cannot + # use :meth:`Statevec.isclose` + assert sv1.fidelity(sv2) == pytest.approx(1.0) + + def test_is_close(self) -> None: + n = 3 + sv1 = Statevec(nqubit=n) + sv2 = Statevec(nqubit=n) + + # generate uniform random statevector + random_psi = np.random.uniform(-1, 1, 2**n) + 1j * np.random.uniform(-1, 1, 2**n) + random_psi /= np.linalg.norm(random_psi) + + sv1.psi = random_psi + sv2.psi = np.copy(sv1.psi) + # global phase should not affect the result + sv2.psi *= np.exp(1j * np.pi / 3) + + assert sv1.isclose(sv2) + def test_normalize() -> None: statevec = Statevec(nqubit=1, data=BasicStates.PLUS) diff --git a/tests/test_statevec_backend.py b/tests/test_statevec_backend.py index 4ca69e3e6..36e59a799 100644 --- a/tests/test_statevec_backend.py +++ b/tests/test_statevec_backend.py @@ -33,7 +33,7 @@ def test_measurement_into_each_xyz_basis(self, state: PlanarState) -> None: sv.remove_qubit(k) sv2 = Statevec(nqubit=n - 1) - assert np.abs(sv.psi.flatten().dot(sv2.psi.flatten().conj())) == pytest.approx(1) + assert sv.isclose(sv2) def test_measurement_into_minus_state(self) -> None: n = 3 diff --git a/tests/test_tnsim.py b/tests/test_tnsim.py index c671cfa87..6e754fca3 100644 --- a/tests/test_tnsim.py +++ b/tests/test_tnsim.py @@ -379,6 +379,8 @@ def test_coef_state(self, fx_bg: PCG64, jumps: int, fx_rng: Generator) -> None: @pytest.mark.parametrize(("nqubits", "jumps"), itertools.product(range(2, 6), range(1, 6))) def test_to_statevector(self, fx_bg: PCG64, nqubits: int, jumps: int, fx_rng: Generator) -> None: + from graphix.sim.statevec import Statevec + rng = Generator(fx_bg.jumped(jumps)) circuit = rand_circuit(nqubits, 3, rng) pattern = circuit.transpile().pattern @@ -389,8 +391,7 @@ def test_to_statevector(self, fx_bg: PCG64, nqubits: int, jumps: int, fx_rng: Ge tn = pattern.simulate_pattern("tensornetwork", rng=fx_rng) statevec_tn = tn.to_statevector() - inner_product = np.inner(statevec_tn, statevec_ref.flatten().conjugate()) - assert abs(inner_product) == pytest.approx(1) + assert Statevec(data=statevec_tn).isclose(statevec_ref) @pytest.mark.parametrize("jumps", range(1, 11)) def test_evolve(self, fx_bg: PCG64, jumps: int, fx_rng: Generator) -> None: diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index 4651eb9ef..12fbc69b4 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -49,7 +49,7 @@ def test_cz(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) def test_cnot(self, fx_rng: Generator) -> None: circuit = Circuit(2) @@ -57,7 +57,7 @@ def test_cnot(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) def test_hadamard(self, fx_rng: Generator) -> None: circuit = Circuit(1) @@ -65,7 +65,7 @@ def test_hadamard(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) def test_s(self, fx_rng: Generator) -> None: circuit = Circuit(1) @@ -73,7 +73,7 @@ def test_s(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) def test_x(self, fx_rng: Generator) -> None: circuit = Circuit(1) @@ -81,7 +81,7 @@ def test_x(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) def test_y(self, fx_rng: Generator) -> None: circuit = Circuit(1) @@ -89,7 +89,7 @@ def test_y(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) def test_z(self, fx_rng: Generator) -> None: circuit = Circuit(1) @@ -97,7 +97,7 @@ def test_z(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) def test_rx(self, fx_rng: Generator) -> None: theta = fx_rng.uniform() * 2 * ANGLE_PI @@ -106,7 +106,7 @@ def test_rx(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) def test_ry(self, fx_rng: Generator) -> None: theta = fx_rng.uniform() * 2 * ANGLE_PI @@ -115,7 +115,7 @@ def test_ry(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) def test_rz(self, fx_rng: Generator) -> None: theta = fx_rng.uniform() * 2 * ANGLE_PI @@ -124,7 +124,7 @@ def test_rz(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) def test_i(self, fx_rng: Generator) -> None: circuit = Circuit(1) @@ -132,7 +132,7 @@ def test_i(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) @pytest.mark.parametrize("jumps", range(1, 11)) def test_ccx(self, fx_bg: PCG64, jumps: int) -> None: @@ -144,7 +144,7 @@ def test_ccx(self, fx_bg: PCG64, jumps: int) -> None: pattern.minimize_space() state = circuit.simulate_statevector(rng=rng).statevec state_mbqc = pattern.simulate_pattern(rng=rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) def test_transpiled(self, fx_rng: Generator) -> None: nqubits = 2 @@ -154,7 +154,7 @@ def test_transpiled(self, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern state = circuit.simulate_statevector(rng=fx_rng).statevec state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) @pytest.mark.parametrize("jumps", range(1, 11)) @pytest.mark.parametrize("axis", [Axis.X, Axis.Y, Axis.Z]) @@ -168,7 +168,7 @@ def test_measure(self, fx_bg: PCG64, jumps: int, axis: Axis, outcome: Outcome) - state = circuit.simulate_statevector(rng=rng, input_state=input_state, branch_selector=branch_selector).statevec pattern = circuit.transpile().pattern state_mbqc = pattern.simulate_pattern(rng=rng, input_state=input_state, branch_selector=branch_selector) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) @pytest.mark.parametrize("input_axis", [Axis.X, Axis.Y, Axis.Z]) @pytest.mark.parametrize("input_sign", [Sign.PLUS, Sign.MINUS]) @@ -208,7 +208,7 @@ def test_transpile_measurements_to_z_axis(self, fx_bg: PCG64, jumps: int, axis: state_z = circuit.simulate_statevector( rng=rng, input_state=input_state, branch_selector=branch_selector ).statevec - assert np.abs(np.dot(state_z.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_z.isclose(state) def test_add_extend(self) -> None: circuit = Circuit(3) @@ -246,4 +246,4 @@ def test_instructions(self, fx_bg: PCG64, jumps: int, instruction: InstructionTe input_state = rand_state_vector(3, rng=rng) state = circuit.simulate_statevector(input_state=input_state).statevec state_mbqc = pattern.simulate_pattern(input_state=input_state, rng=rng) - assert np.abs(np.dot(state_mbqc.flatten().conjugate(), state.flatten())) == pytest.approx(1) + assert state_mbqc.isclose(state) From 52092b3503ebea462d5b0ac7ecf69e768b3a6d5a Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Mon, 16 Feb 2026 07:39:06 +0800 Subject: [PATCH 2/3] - Removed accidental assertion. --- tests/test_pyzx.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_pyzx.py b/tests/test_pyzx.py index 57aae7270..37bf4f420 100644 --- a/tests/test_pyzx.py +++ b/tests/test_pyzx.py @@ -129,4 +129,3 @@ def test_full_reduce_toffoli() -> None: s = p.simulate_pattern() s2 = p2.simulate_pattern() print(s.fidelity(s2)) - assert s.isclose(s2) From e36a4e11f2982b2bc5a488bd1d7b88a9367b0734 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Mon, 16 Feb 2026 17:51:04 +0800 Subject: [PATCH 3/3] resolved ruff and mypy issues --- graphix/sim/statevec.py | 2 +- tests/test_flow_core.py | 1 - tests/test_graphsim.py | 1 - tests/test_opengraph.py | 1 - tests/test_optimization.py | 1 - tests/test_statevec.py | 8 ++++---- tests/test_tnsim.py | 3 +-- 7 files changed, 6 insertions(+), 11 deletions(-) diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 6385bb3f7..0147afbeb 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -421,7 +421,7 @@ def fidelity(self, other: Statevec) -> float: st1.normalize() st2 = copy.copy(other) st2.normalize() - return float(np.abs(np.vdot(st1.psi.flatten(), st2.psi.flatten()))) ** 2 + return float(np.abs(np.vdot(st1.psi.flatten(), st2.psi.flatten()))) ** 2 # type: ignore[arg-type] def isclose(self, other: Statevec, atol: float = 1e-15, rtol: float = 1e-10) -> bool: r"""Return whether the state is equal to another state up to global phase. diff --git a/tests/test_flow_core.py b/tests/test_flow_core.py index e2d1555b5..a45bc36dd 100644 --- a/tests/test_flow_core.py +++ b/tests/test_flow_core.py @@ -4,7 +4,6 @@ from typing import TYPE_CHECKING, NamedTuple import networkx as nx -import numpy as np import pytest from graphix.command import E, M, N, X, Z diff --git a/tests/test_graphsim.py b/tests/test_graphsim.py index 4c247138f..bfb4ae4c2 100644 --- a/tests/test_graphsim.py +++ b/tests/test_graphsim.py @@ -5,7 +5,6 @@ import networkx as nx import numpy as np import numpy.typing as npt -import pytest from graphix.clifford import Clifford from graphix.fundamentals import ANGLE_PI, Plane, angle_to_rad diff --git a/tests/test_opengraph.py b/tests/test_opengraph.py index cd830fadf..21d42ee06 100644 --- a/tests/test_opengraph.py +++ b/tests/test_opengraph.py @@ -10,7 +10,6 @@ from typing import TYPE_CHECKING, NamedTuple import networkx as nx -import numpy as np import pytest from graphix.command import E diff --git a/tests/test_optimization.py b/tests/test_optimization.py index ca65405fe..4ea4d9260 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -1,6 +1,5 @@ from __future__ import annotations -import numpy as np import pytest from numpy.random import PCG64, Generator diff --git a/tests/test_statevec.py b/tests/test_statevec.py index e6aa70ca4..7a18361e5 100644 --- a/tests/test_statevec.py +++ b/tests/test_statevec.py @@ -150,13 +150,13 @@ def test_fidelity(self) -> None: sv2 = Statevec(nqubit=n) # generate uniform random statevector - random_psi = np.random.uniform(-1, 1, 2**n) + 1j * np.random.uniform(-1, 1, 2**n) + random_psi = np.random.uniform(-1, 1, 2**n) + 1j * np.random.uniform(-1, 1, 2**n) # noqa: NPY002 random_psi /= np.linalg.norm(random_psi) sv1.psi = random_psi sv2.psi = np.copy(sv1.psi) # global phase should not affect the result - sv2.psi *= np.exp(1j * np.pi / 3) + sv2.psi *= np.exp(1j * np.pi / 3) # type: ignore[misc] # We are unit testing :meth:`Statevec.fidelity`, so we cannot # use :meth:`Statevec.isclose` @@ -168,13 +168,13 @@ def test_is_close(self) -> None: sv2 = Statevec(nqubit=n) # generate uniform random statevector - random_psi = np.random.uniform(-1, 1, 2**n) + 1j * np.random.uniform(-1, 1, 2**n) + random_psi = np.random.uniform(-1, 1, 2**n) + 1j * np.random.uniform(-1, 1, 2**n) # noqa: NPY002 random_psi /= np.linalg.norm(random_psi) sv1.psi = random_psi sv2.psi = np.copy(sv1.psi) # global phase should not affect the result - sv2.psi *= np.exp(1j * np.pi / 3) + sv2.psi *= np.exp(1j * np.pi / 3) # type: ignore[misc] assert sv1.isclose(sv2) diff --git a/tests/test_tnsim.py b/tests/test_tnsim.py index 6e754fca3..15a83b971 100644 --- a/tests/test_tnsim.py +++ b/tests/test_tnsim.py @@ -14,6 +14,7 @@ from graphix.fundamentals import ANGLE_PI from graphix.ops import Ops from graphix.random_objects import rand_circuit +from graphix.sim.statevec import Statevec from graphix.sim.tensornet import MBQCTensorNet, gen_str from graphix.states import BasicStates from graphix.transpiler import Circuit @@ -379,8 +380,6 @@ def test_coef_state(self, fx_bg: PCG64, jumps: int, fx_rng: Generator) -> None: @pytest.mark.parametrize(("nqubits", "jumps"), itertools.product(range(2, 6), range(1, 6))) def test_to_statevector(self, fx_bg: PCG64, nqubits: int, jumps: int, fx_rng: Generator) -> None: - from graphix.sim.statevec import Statevec - rng = Generator(fx_bg.jumped(jumps)) circuit = rand_circuit(nqubits, 3, rng) pattern = circuit.transpile().pattern