From e72dbf410f0e2072c6b6993d49c7b89d5ce907fc Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Tue, 30 Aug 2022 19:29:02 -0700 Subject: [PATCH 1/4] Fix phase in factor --- cirq-core/cirq/linalg/transformations.py | 6 +++--- cirq-core/cirq/sim/sparse_simulator_test.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/cirq-core/cirq/linalg/transformations.py b/cirq-core/cirq/linalg/transformations.py index 1a1b8c257e0..be91bf87688 100644 --- a/cirq-core/cirq/linalg/transformations.py +++ b/cirq-core/cirq/linalg/transformations.py @@ -589,12 +589,12 @@ def factor_state_vector( slices1 = (slice(None),) * n_axes + pivot[n_axes:] slices2 = pivot[:n_axes] + (slice(None),) * (t1.ndim - n_axes) extracted = t1[slices1] - extracted = extracted / np.sum(abs(extracted) ** 2) ** 0.5 + extracted = extracted / np.linalg.norm(extracted) remainder = t1[slices2] - remainder = remainder / np.sum(abs(remainder) ** 2) ** 0.5 + remainder = remainder / (np.linalg.norm(remainder) * t1[pivot] / abs(t1[pivot])) if validate: t2 = state_vector_kronecker_product(extracted, remainder) - if not predicates.allclose_up_to_global_phase(t2, t1, atol=atol): + if not np.allclose(t2, t1, atol=atol): if not np.isclose(np.linalg.norm(t1), 1): raise ValueError('Input state must be normalized.') raise EntangledStateError('The tensor cannot be factored by the requested axes') diff --git a/cirq-core/cirq/sim/sparse_simulator_test.py b/cirq-core/cirq/sim/sparse_simulator_test.py index 1ebef6b4240..5f7a258413b 100644 --- a/cirq-core/cirq/sim/sparse_simulator_test.py +++ b/cirq-core/cirq/sim/sparse_simulator_test.py @@ -1434,3 +1434,22 @@ def test_unseparated_states_str(): qubits: (cirq.LineQubit(0), cirq.LineQubit(1)) output vector: 0.707j|00⟩ + 0.707j|10⟩""" ) + + +@pytest.mark.parametrize('split', [True, False]) +def test_measurement_preserves_phase(split: bool): + c1, c2, t = cirq.LineQubit.range(3) + circuit = cirq.Circuit( + cirq.H(t), + cirq.measure(t, key="t"), + cirq.CZ(c1, c2).with_classical_controls("t"), + cirq.reset(t), + ) + simulator = cirq.Simulator(split_untangled_states=split) + # Run enough times that both options of |110> - |111> are likely measured. + for _ in range(20): + for step in simulator.simulate_moment_steps( + circuit, initial_state=(1, 1, 1), qubit_order=(c1, c2, t) + ): + pass + assert step.dirac_notation() == "|110⟩" From 174e80cd6e830e16b10bb53534f5710c734f5c1b Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Tue, 30 Aug 2022 21:51:05 -0700 Subject: [PATCH 2/4] Add test --- cirq-core/cirq/linalg/transformations_test.py | 21 +++++++++++++++++++ .../cirq/sim/simulation_product_state.py | 2 +- cirq-core/cirq/sim/sparse_simulator_test.py | 7 ++----- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/cirq-core/cirq/linalg/transformations_test.py b/cirq-core/cirq/linalg/transformations_test.py index 6e13fb64d62..8c00db824e2 100644 --- a/cirq-core/cirq/linalg/transformations_test.py +++ b/cirq-core/cirq/linalg/transformations_test.py @@ -613,3 +613,24 @@ def test_default_tolerance(): # Here, we do NOT specify the default tolerance. It is merely to check that the default value # is reasonable. cirq.sub_state_vector(final_state_vector, [0]) + + +@pytest.mark.parametrize('state_1', [0, 1]) +@pytest.mark.parametrize('state_2', [0, 1]) +def test_factor_state_vector(state_1: int, state_2: int): + # Kron two state vectors each with some phase. Factoring should produce the expected results. + n = 12 + for i in range(n): + phase1 = np.exp(2 * np.pi * 1j * i / n) + for j in range(n): + phase2 = np.exp(2 * np.pi * 1j * j / n) + a = cirq.to_valid_state_vector(state_1, 1) * phase1 + b = cirq.to_valid_state_vector(state_2, 1) * phase2 + c = cirq.linalg.transformations.state_vector_kronecker_product(a, b) + a1, b1 = cirq.linalg.transformations.factor_state_vector(c, [0], validate=True) + c1 = cirq.linalg.transformations.state_vector_kronecker_product(a1, b1) + assert np.allclose(c, c1) + + # All phase goes into a1, and b1 is just the dephased state vector + assert np.allclose(a1, cirq.to_valid_state_vector(state_1, 1) * phase1 * phase2) + assert np.allclose(b1, cirq.to_valid_state_vector(state_2, 1)) diff --git a/cirq-core/cirq/sim/simulation_product_state.py b/cirq-core/cirq/sim/simulation_product_state.py index 1338d7ebf7a..c4421b41ec2 100644 --- a/cirq-core/cirq/sim/simulation_product_state.py +++ b/cirq-core/cirq/sim/simulation_product_state.py @@ -122,7 +122,7 @@ def _act_on_fallback_( gate_opt, (ops.ResetChannel, ops.MeasurementGate) ): for q in qubits: - if op_args.allows_factoring: + if op_args.allows_factoring and len(op_args.qubits) > 1: q_args, op_args = op_args.factor((q,), validate=False) self._sim_states[q] = q_args diff --git a/cirq-core/cirq/sim/sparse_simulator_test.py b/cirq-core/cirq/sim/sparse_simulator_test.py index 5f7a258413b..0f4628c8075 100644 --- a/cirq-core/cirq/sim/sparse_simulator_test.py +++ b/cirq-core/cirq/sim/sparse_simulator_test.py @@ -1448,8 +1448,5 @@ def test_measurement_preserves_phase(split: bool): simulator = cirq.Simulator(split_untangled_states=split) # Run enough times that both options of |110> - |111> are likely measured. for _ in range(20): - for step in simulator.simulate_moment_steps( - circuit, initial_state=(1, 1, 1), qubit_order=(c1, c2, t) - ): - pass - assert step.dirac_notation() == "|110⟩" + result = simulator.simulate(circuit, initial_state=(1, 1, 1), qubit_order=(c1, c2, t)) + assert result.dirac_notation() == "|110⟩" From 1c09f2e76ae0a92101c4c85d8f67655b212741a4 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Tue, 30 Aug 2022 22:20:37 -0700 Subject: [PATCH 3/4] tolerance --- cirq-core/cirq/linalg/transformations_test.py | 26 +++++++++---------- docs/experiments/textbook_algorithms.ipynb | 2 +- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/cirq-core/cirq/linalg/transformations_test.py b/cirq-core/cirq/linalg/transformations_test.py index 8c00db824e2..b92ff5faa13 100644 --- a/cirq-core/cirq/linalg/transformations_test.py +++ b/cirq-core/cirq/linalg/transformations_test.py @@ -618,19 +618,17 @@ def test_default_tolerance(): @pytest.mark.parametrize('state_1', [0, 1]) @pytest.mark.parametrize('state_2', [0, 1]) def test_factor_state_vector(state_1: int, state_2: int): - # Kron two state vectors each with some phase. Factoring should produce the expected results. + # Kron two state vectors and apply a phase. Factoring should produce the expected results. n = 12 for i in range(n): - phase1 = np.exp(2 * np.pi * 1j * i / n) - for j in range(n): - phase2 = np.exp(2 * np.pi * 1j * j / n) - a = cirq.to_valid_state_vector(state_1, 1) * phase1 - b = cirq.to_valid_state_vector(state_2, 1) * phase2 - c = cirq.linalg.transformations.state_vector_kronecker_product(a, b) - a1, b1 = cirq.linalg.transformations.factor_state_vector(c, [0], validate=True) - c1 = cirq.linalg.transformations.state_vector_kronecker_product(a1, b1) - assert np.allclose(c, c1) - - # All phase goes into a1, and b1 is just the dephased state vector - assert np.allclose(a1, cirq.to_valid_state_vector(state_1, 1) * phase1 * phase2) - assert np.allclose(b1, cirq.to_valid_state_vector(state_2, 1)) + phase = np.exp(2 * np.pi * 1j * i / n) + a = cirq.to_valid_state_vector(state_1, 1) + b = cirq.to_valid_state_vector(state_2, 1) + c = cirq.linalg.transformations.state_vector_kronecker_product(a, b) * phase + a1, b1 = cirq.linalg.transformations.factor_state_vector(c, [0], validate=True) + c1 = cirq.linalg.transformations.state_vector_kronecker_product(a1, b1) + assert np.allclose(c, c1) + + # All phase goes into a1, and b1 is just the dephased state vector + assert np.allclose(a1, a * phase) + assert np.allclose(b1, b) diff --git a/docs/experiments/textbook_algorithms.ipynb b/docs/experiments/textbook_algorithms.ipynb index 2287dcc5fad..0949421cf64 100644 --- a/docs/experiments/textbook_algorithms.ipynb +++ b/docs/experiments/textbook_algorithms.ipynb @@ -233,7 +233,7 @@ "print(np.round(bobs_bloch_vector, 3))\n", "\n", "# Verify they are the same state!\n", - "np.testing.assert_allclose(bobs_bloch_vector, message_bloch_vector, atol=1e-7)" + "np.testing.assert_allclose(bobs_bloch_vector, message_bloch_vector, atol=1e-6)" ] }, { From 55d3b0770e4084932742c94a3bd43a70dca28e1d Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Tue, 30 Aug 2022 22:33:12 -0700 Subject: [PATCH 4/4] single quote --- cirq-core/cirq/sim/sparse_simulator_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cirq-core/cirq/sim/sparse_simulator_test.py b/cirq-core/cirq/sim/sparse_simulator_test.py index 0f4628c8075..ee16e285313 100644 --- a/cirq-core/cirq/sim/sparse_simulator_test.py +++ b/cirq-core/cirq/sim/sparse_simulator_test.py @@ -1441,12 +1441,12 @@ def test_measurement_preserves_phase(split: bool): c1, c2, t = cirq.LineQubit.range(3) circuit = cirq.Circuit( cirq.H(t), - cirq.measure(t, key="t"), - cirq.CZ(c1, c2).with_classical_controls("t"), + cirq.measure(t, key='t'), + cirq.CZ(c1, c2).with_classical_controls('t'), cirq.reset(t), ) simulator = cirq.Simulator(split_untangled_states=split) # Run enough times that both options of |110> - |111> are likely measured. for _ in range(20): result = simulator.simulate(circuit, initial_state=(1, 1, 1), qubit_order=(c1, c2, t)) - assert result.dirac_notation() == "|110⟩" + assert result.dirac_notation() == '|110⟩'