From 07d53bc7840e4a6932be45ed2af2b92328a969cb Mon Sep 17 00:00:00 2001 From: oscarhiggott <29460323+oscarhiggott@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:09:02 -0700 Subject: [PATCH] Test for either ValueError or TypeError when loading scipy.sparse (#95) * Test for either ValueError or TypeError when loading scipy.sparse with invalid input. Also format. * remove unnecessary universal2 wheel builds (these currently cause an import error in the CI builds) --- .github/workflows/ci.yml | 10 +- tests/matching/load_from_check_matrix_test.py | 392 ++++++++++++------ 2 files changed, 260 insertions(+), 142 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e83974d..48159d2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,11 +55,11 @@ jobs: {os: macos-latest, dist: cp311-macosx_arm64}, {os: macos-latest, dist: cp312-macosx_arm64}, # macosx universal2 - {os: macos-latest, dist: cp38-macosx_universal2}, - {os: macos-latest, dist: cp39-macosx_universal2}, - {os: macos-latest, dist: cp310-macosx_universal2}, - {os: macos-latest, dist: cp311-macosx_universal2}, - {os: macos-latest, dist: cp312-macosx_universal2}, + # {os: macos-latest, dist: cp38-macosx_universal2}, + # {os: macos-latest, dist: cp39-macosx_universal2}, + # {os: macos-latest, dist: cp310-macosx_universal2}, + # {os: macos-latest, dist: cp311-macosx_universal2}, + # {os: macos-latest, dist: cp312-macosx_universal2}, # windows amd64 # {os: windows-latest, dist: cp36-win_amd64}, diff --git a/tests/matching/load_from_check_matrix_test.py b/tests/matching/load_from_check_matrix_test.py index 594e90c1..8193fed2 100644 --- a/tests/matching/load_from_check_matrix_test.py +++ b/tests/matching/load_from_check_matrix_test.py @@ -21,9 +21,12 @@ def test_boundary_from_check_matrix(): - H = csr_matrix(np.array([[1, 1, 0, 0, 0], [0, 1, 1, 0, 0], - [0, 0, 1, 1, 0], [0, 0, 0, 1, 1]])) - m = Matching(H=H) # Checks `H` still accepted as keyword argument despite being deprecated + H = csr_matrix( + np.array([[1, 1, 0, 0, 0], [0, 1, 1, 0, 0], [0, 0, 1, 1, 0], [0, 0, 0, 1, 1]]) + ) + m = Matching( + H=H + ) # Checks `H` still accepted as keyword argument despite being deprecated assert m.boundary == {4} assert np.array_equal(m.decode(np.array([1, 0, 0, 0])), np.array([1, 0, 0, 0, 0])) assert np.array_equal(m.decode(np.array([0, 1, 0, 0])), np.array([1, 1, 0, 0, 0])) @@ -38,28 +41,27 @@ def test_nonzero_matrix_elements_not_one_raises_value_error(): def test_too_many_checks_per_column_raises_value_error(): - H = csr_matrix(np.array([[1, 1, 0, 0], - [1, 0, 1, 0], - [1, 0, 0, 1]])) + H = csr_matrix(np.array([[1, 1, 0, 0], [1, 0, 1, 0], [1, 0, 0, 1]])) with pytest.raises(ValueError): Matching(H) def test_wrong_check_matrix_type_raises_type_error(): - with pytest.raises(TypeError): + with pytest.raises((TypeError, ValueError)): Matching("test") m = Matching() - with pytest.raises(TypeError): + with pytest.raises((TypeError, ValueError)): m.load_from_check_matrix("test") def test_error_probability_from_array(): - H = csr_matrix(np.array([[1, 1, 0, 0, 0], [0, 1, 1, 0, 0], - [0, 0, 1, 1, 0], [0, 0, 0, 1, 1]])) - m = Matching(H, error_probabilities=np.array([0., 0., 0., 0., 1.])) + H = csr_matrix( + np.array([[1, 1, 0, 0, 0], [0, 1, 1, 0, 0], [0, 0, 1, 1, 0], [0, 0, 0, 1, 1]]) + ) + m = Matching(H, error_probabilities=np.array([0.0, 0.0, 0.0, 0.0, 1.0])) assert np.array_equal(m.add_noise()[0], np.array([0, 0, 0, 0, 1])) assert np.array_equal(m.add_noise()[1], np.array([0, 0, 0, 1, 0])) - m = Matching(H, error_probabilities=np.array([0., 0., 0., 0., 0.])) + m = Matching(H, error_probabilities=np.array([0.0, 0.0, 0.0, 0.0, 0.0])) assert np.array_equal(m.add_noise()[0], np.array([0, 0, 0, 0, 0])) assert np.array_equal(m.add_noise()[1], np.array([0, 0, 0, 0, 0])) m = Matching(H, error_probabilities=0.0) @@ -72,9 +74,9 @@ def test_error_probability_from_array(): def test_weighted_mwpm_from_array(): H = csc_matrix([[1, 0], [1, 1], [0, 1]]) - Matching(H, spacelike_weights=np.array([1., 2.])) + Matching(H, spacelike_weights=np.array([1.0, 2.0])) with pytest.raises(ValueError): - Matching(H, spacelike_weights=np.array([1.])) + Matching(H, spacelike_weights=np.array([1.0])) def test_load_matching_from_dense_array(): @@ -83,36 +85,74 @@ def test_load_matching_from_dense_array(): m.load_from_check_matrix(H) -@pytest.mark.parametrize("t_weights,expected_edges", - [ - ( - [0.5, 1.5], - {((0, 1), 0.7), ((2, 3), 0.7), ((4, 5), 0.7), - ((0, 6), 0.3), ((2, 6), 0.3), ((4, 6), 0.3), - ((1, 6), 0.9), ((3, 6), 0.9), ((5, 6), 0.9), - ((0, 2), 0.5), ((2, 4), 0.5), ((1, 3), 1.5), ((3, 5), 1.5)} - ), - ( - np.array([0.5, 1.5]), - {((0, 1), 0.7), ((2, 3), 0.7), ((4, 5), 0.7), - ((0, 6), 0.3), ((2, 6), 0.3), ((4, 6), 0.3), - ((1, 6), 0.9), ((3, 6), 0.9), ((5, 6), 0.9), - ((0, 2), 0.5), ((2, 4), 0.5), ((1, 3), 1.5), ((3, 5), 1.5)} - ), - ( - 1.2, - {((0, 1), 0.7), ((2, 3), 0.7), ((4, 5), 0.7), - ((0, 6), 0.3), ((2, 6), 0.3), ((4, 6), 0.3), - ((1, 6), 0.9), ((3, 6), 0.9), ((5, 6), 0.9), - ((0, 2), 1.2), ((2, 4), 1.2), ((1, 3), 1.2), ((3, 5), 1.2)} - ) - ] - ) +@pytest.mark.parametrize( + "t_weights,expected_edges", + [ + ( + [0.5, 1.5], + { + ((0, 1), 0.7), + ((2, 3), 0.7), + ((4, 5), 0.7), + ((0, 6), 0.3), + ((2, 6), 0.3), + ((4, 6), 0.3), + ((1, 6), 0.9), + ((3, 6), 0.9), + ((5, 6), 0.9), + ((0, 2), 0.5), + ((2, 4), 0.5), + ((1, 3), 1.5), + ((3, 5), 1.5), + }, + ), + ( + np.array([0.5, 1.5]), + { + ((0, 1), 0.7), + ((2, 3), 0.7), + ((4, 5), 0.7), + ((0, 6), 0.3), + ((2, 6), 0.3), + ((4, 6), 0.3), + ((1, 6), 0.9), + ((3, 6), 0.9), + ((5, 6), 0.9), + ((0, 2), 0.5), + ((2, 4), 0.5), + ((1, 3), 1.5), + ((3, 5), 1.5), + }, + ), + ( + 1.2, + { + ((0, 1), 0.7), + ((2, 3), 0.7), + ((4, 5), 0.7), + ((0, 6), 0.3), + ((2, 6), 0.3), + ((4, 6), 0.3), + ((1, 6), 0.9), + ((3, 6), 0.9), + ((5, 6), 0.9), + ((0, 2), 1.2), + ((2, 4), 1.2), + ((1, 3), 1.2), + ((3, 5), 1.2), + }, + ), + ], +) def test_timelike_weights(t_weights, expected_edges): H = np.array([[1, 1, 0], [0, 1, 1]]) m = Matching() - m.load_from_check_matrix(H, spacelike_weights=np.array([0.3, 0.7, 0.9]), - timelike_weights=t_weights, repetitions=3) + m.load_from_check_matrix( + H, + spacelike_weights=np.array([0.3, 0.7, 0.9]), + timelike_weights=t_weights, + repetitions=3, + ) es = set((tuple(sorted([u, v])), d["weight"]) for u, v, d in m.edges()) assert es == expected_edges @@ -122,44 +162,72 @@ def test_wrong_timelike_weights_raises_valueerror(t_weights): H = np.array([[1, 1, 0], [0, 1, 1]]) with pytest.raises(ValueError): m = Matching() - m.load_from_check_matrix(H, spacelike_weights=np.array([0.3, 0.7, 0.9]), - timelike_weights=t_weights, repetitions=3) - - -@pytest.mark.parametrize("p_meas,expected_edges,repetitions", - [ - ( - [0.15, 0.25], - {((0, 1), 0.2), ((2, 3), 0.2), ((4, 5), 0.2), - ((0, 6), 0.1), ((2, 6), 0.1), ((4, 6), 0.1), - ((1, 6), 0.3), ((3, 6), 0.3), ((5, 6), 0.3), - ((0, 2), 0.15), ((2, 4), 0.15), ((1, 3), 0.25), ((3, 5), 0.25)}, - 3 - ), - ( - np.array([0.15, 0.25]), - {((0, 1), 0.2), ((2, 3), 0.2), - ((0, 4), 0.1), ((2, 4), 0.1), - ((1, 4), 0.3), ((3, 4), 0.3), - ((0, 2), 0.15), ((1, 3), 0.25)}, - 2 - ), - ( - 0.17, - {((0, 1), 0.2), ((2, 3), 0.2), - ((0, 4), 0.1), ((2, 4), 0.1), - ((1, 4), 0.3), ((3, 4), 0.3), - ((0, 2), 0.17), ((1, 3), 0.17)}, - 2 - ) - ] - ) + m.load_from_check_matrix( + H, + spacelike_weights=np.array([0.3, 0.7, 0.9]), + timelike_weights=t_weights, + repetitions=3, + ) + + +@pytest.mark.parametrize( + "p_meas,expected_edges,repetitions", + [ + ( + [0.15, 0.25], + { + ((0, 1), 0.2), + ((2, 3), 0.2), + ((4, 5), 0.2), + ((0, 6), 0.1), + ((2, 6), 0.1), + ((4, 6), 0.1), + ((1, 6), 0.3), + ((3, 6), 0.3), + ((5, 6), 0.3), + ((0, 2), 0.15), + ((2, 4), 0.15), + ((1, 3), 0.25), + ((3, 5), 0.25), + }, + 3, + ), + ( + np.array([0.15, 0.25]), + { + ((0, 1), 0.2), + ((2, 3), 0.2), + ((0, 4), 0.1), + ((2, 4), 0.1), + ((1, 4), 0.3), + ((3, 4), 0.3), + ((0, 2), 0.15), + ((1, 3), 0.25), + }, + 2, + ), + ( + 0.17, + { + ((0, 1), 0.2), + ((2, 3), 0.2), + ((0, 4), 0.1), + ((2, 4), 0.1), + ((1, 4), 0.3), + ((3, 4), 0.3), + ((0, 2), 0.17), + ((1, 3), 0.17), + }, + 2, + ), + ], +) def test_measurement_error_probabilities(p_meas, expected_edges, repetitions): m = Matching( [[1, 1, 0], [0, 1, 1]], error_probabilities=[0.1, 0.2, 0.3], measurement_error_probabilities=p_meas, - repetitions=repetitions + repetitions=repetitions, ) es = set((tuple(sorted([u, v])), d["error_probability"]) for u, v, d in m.edges()) assert es == expected_edges @@ -169,7 +237,7 @@ def test_measurement_error_probabilities(p_meas, expected_edges, repetitions): [[1, 1, 0], [0, 1, 1]], error_probabilities=[0.1, 0.2, 0.3], measurement_error_probability=p_meas, - repetitions=repetitions + repetitions=repetitions, ) es = set((tuple(sorted([u, v])), d["error_probability"]) for u, v, d in m.edges()) assert es == expected_edges @@ -180,28 +248,47 @@ def test_wrong_measurement_error_probabilities_raises_valueerror(m_errors): H = np.array([[1, 1, 0], [0, 1, 1]]) with pytest.raises(ValueError): m = Matching() - m.load_from_check_matrix(H, weights=np.array([0.3, 0.7, 0.9]), - measurement_error_probabilities=m_errors, repetitions=3) + m.load_from_check_matrix( + H, + weights=np.array([0.3, 0.7, 0.9]), + measurement_error_probabilities=m_errors, + repetitions=3, + ) def test_measurement_error_probabilities_and_probability_raises_value_error(): H = np.array([[1, 1, 0], [0, 1, 1]]) with pytest.raises(ValueError): m = Matching() - m.load_from_check_matrix(H, spacelike_weights=np.array([0.3, 0.7, 0.9]), - measurement_error_probabilities=[0.1, 0.1], repetitions=3, - measurement_error_probability=[0.1, 0.1]) + m.load_from_check_matrix( + H, + spacelike_weights=np.array([0.3, 0.7, 0.9]), + measurement_error_probabilities=[0.1, 0.1], + repetitions=3, + measurement_error_probability=[0.1, 0.1], + ) def test_cpp_csc_matrix_to_matching_graph(): - H = csc_matrix(np.array([[1, 1, 0, 0, 0, 0], - [0, 1, 1, 0, 0, 1], - [0, 0, 1, 1, 0, 1], - [0, 0, 0, 1, 1, 0]])) + H = csc_matrix( + np.array( + [ + [1, 1, 0, 0, 0, 0], + [0, 1, 1, 0, 0, 1], + [0, 0, 1, 1, 0, 1], + [0, 0, 0, 1, 1, 0], + ] + ) + ) weights = np.array([1.0, 2.0, 3.0, 2.0, 1.5, 5.0]) error_probabilities = np.array([0.4, 0.3, 0.4, 0.2, 0.1, 0.01]) - g = sparse_column_check_matrix_to_matching_graph(H, weights, error_probabilities, merge_strategy="smallest-weight", - use_virtual_boundary_node=False) + g = sparse_column_check_matrix_to_matching_graph( + H, + weights, + error_probabilities, + merge_strategy="smallest-weight", + use_virtual_boundary_node=False, + ) assert g.get_edges() == [ (0, 4, {"fault_ids": {0}, "weight": 1.0, "error_probability": 0.4}), (0, 1, {"fault_ids": {1}, "weight": 2.0, "error_probability": 0.3}), @@ -213,11 +300,20 @@ def test_cpp_csc_matrix_to_matching_graph(): assert g.get_num_nodes() == 5 with pytest.raises(ValueError): # Check that scipy.sparse.csr_matrix is not accepted - g = sparse_column_check_matrix_to_matching_graph(csr_matrix(H.T), weights, error_probabilities, - merge_strategy="smallest-weight", - use_virtual_boundary_node=False) - g = sparse_column_check_matrix_to_matching_graph(H, weights, error_probabilities, merge_strategy="replace", - use_virtual_boundary_node=True) + g = sparse_column_check_matrix_to_matching_graph( + csr_matrix(H.T), + weights, + error_probabilities, + merge_strategy="smallest-weight", + use_virtual_boundary_node=False, + ) + g = sparse_column_check_matrix_to_matching_graph( + H, + weights, + error_probabilities, + merge_strategy="replace", + use_virtual_boundary_node=True, + ) assert g.get_edges() == [ (0, None, {"fault_ids": {0}, "weight": 1.0, "error_probability": 0.4}), (0, 1, {"fault_ids": {1}, "weight": 2.0, "error_probability": 0.3}), @@ -229,63 +325,81 @@ def test_cpp_csc_matrix_to_matching_graph(): assert g.get_num_nodes() == 4 p_meas = np.array([0.1, 0.2, 0.15, 0.25]) t_weights = np.log((1 - p_meas) / p_meas) - g = sparse_column_check_matrix_to_matching_graph(H, weights, error_probabilities, merge_strategy="smallest-weight", - use_virtual_boundary_node=False, num_repetitions=3, - timelike_weights=t_weights, - measurement_error_probabilities=t_weights - ) + g = sparse_column_check_matrix_to_matching_graph( + H, + weights, + error_probabilities, + merge_strategy="smallest-weight", + use_virtual_boundary_node=False, + num_repetitions=3, + timelike_weights=t_weights, + measurement_error_probabilities=t_weights, + ) with pytest.raises(ValueError): p_meas = np.array([[0.1, 0.2], [0.15, 0.25]]) t_weights = np.log((1 - p_meas) / p_meas) - g = sparse_column_check_matrix_to_matching_graph(H, weights, error_probabilities, - merge_strategy="smallest-weight", - use_virtual_boundary_node=False, num_repetitions=3, - timelike_weights=t_weights, - measurement_error_probabilities=t_weights - ) + g = sparse_column_check_matrix_to_matching_graph( + H, + weights, + error_probabilities, + merge_strategy="smallest-weight", + use_virtual_boundary_node=False, + num_repetitions=3, + timelike_weights=t_weights, + measurement_error_probabilities=t_weights, + ) def test_load_from_check_matrix_with_faults(): - H = csc_matrix(np.array([[1, 1, 0, 0, 0], - [0, 1, 1, 0, 0], - [0, 0, 1, 1, 0], - [0, 0, 0, 1, 1]])) - F = csc_matrix(np.array([[0, 0, 1, 1, 1], - [1, 1, 1, 0, 0]])) + H = csc_matrix( + np.array([[1, 1, 0, 0, 0], [0, 1, 1, 0, 0], [0, 0, 1, 1, 0], [0, 0, 0, 1, 1]]) + ) + F = csc_matrix(np.array([[0, 0, 1, 1, 1], [1, 1, 1, 0, 0]])) m = Matching() m.load_from_check_matrix(H=H, faults_matrix=F) assert m.num_fault_ids == 2 - assert m.edges() == [(0, 4, {'error_probability': -1.0, 'fault_ids': {1}, 'weight': 1.0}), - (0, 1, {'error_probability': -1.0, 'fault_ids': {1}, 'weight': 1.0}), - (1, 2, {'error_probability': -1.0, 'fault_ids': {0, 1}, 'weight': 1.0}), - (2, 3, {'error_probability': -1.0, 'fault_ids': {0}, 'weight': 1.0}), - (3, 4, {'error_probability': -1.0, 'fault_ids': {0}, 'weight': 1.0})] - m = Matching.from_check_matrix([[1, 1, 0], [0, 1, 1]], faults_matrix=[[1, 1, 1]], use_virtual_boundary_node=True) + assert m.edges() == [ + (0, 4, {"error_probability": -1.0, "fault_ids": {1}, "weight": 1.0}), + (0, 1, {"error_probability": -1.0, "fault_ids": {1}, "weight": 1.0}), + (1, 2, {"error_probability": -1.0, "fault_ids": {0, 1}, "weight": 1.0}), + (2, 3, {"error_probability": -1.0, "fault_ids": {0}, "weight": 1.0}), + (3, 4, {"error_probability": -1.0, "fault_ids": {0}, "weight": 1.0}), + ] + m = Matching.from_check_matrix( + [[1, 1, 0], [0, 1, 1]], + faults_matrix=[[1, 1, 1]], + use_virtual_boundary_node=True, + ) assert m.num_fault_ids == 1 - assert m.edges() == [(0, None, {'error_probability': -1.0, 'fault_ids': {0}, 'weight': 1.0}), - (0, 1, {'error_probability': -1.0, 'fault_ids': {0}, 'weight': 1.0}), - (1, None, {'error_probability': -1.0, 'fault_ids': {0}, 'weight': 1.0})] + assert m.edges() == [ + (0, None, {"error_probability": -1.0, "fault_ids": {0}, "weight": 1.0}), + (0, 1, {"error_probability": -1.0, "fault_ids": {0}, "weight": 1.0}), + (1, None, {"error_probability": -1.0, "fault_ids": {0}, "weight": 1.0}), + ] m = Matching() - m.load_from_check_matrix([[1, 1, 0], [0, 1, 1]], faults_matrix=csc_matrix((0, 3), dtype=np.uint8), - use_virtual_boundary_node=True) + m.load_from_check_matrix( + [[1, 1, 0], [0, 1, 1]], + faults_matrix=csc_matrix((0, 3), dtype=np.uint8), + use_virtual_boundary_node=True, + ) assert m.num_fault_ids == 0 - assert m.edges() == [(0, None, {'error_probability': -1.0, 'fault_ids': set(), 'weight': 1.0}), - (0, 1, {'error_probability': -1.0, 'fault_ids': set(), 'weight': 1.0}), - (1, None, {'error_probability': -1.0, 'fault_ids': set(), 'weight': 1.0})] - H = csc_matrix(np.array([[1, 1, 0], - [0, 1, 1], - [0, 0, 1]])) - F = csc_matrix(np.array([[0, 0, 1], - [1, 1, 1], - [1, 0, 1], - [0, 0, 1], - [1, 1, 0]])) - m = Matching.from_check_matrix(check_matrix=H, faults_matrix=F, use_virtual_boundary_node=True) + assert m.edges() == [ + (0, None, {"error_probability": -1.0, "fault_ids": set(), "weight": 1.0}), + (0, 1, {"error_probability": -1.0, "fault_ids": set(), "weight": 1.0}), + (1, None, {"error_probability": -1.0, "fault_ids": set(), "weight": 1.0}), + ] + H = csc_matrix(np.array([[1, 1, 0], [0, 1, 1], [0, 0, 1]])) + F = csc_matrix(np.array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 0, 1], [1, 1, 0]])) + m = Matching.from_check_matrix( + check_matrix=H, faults_matrix=F, use_virtual_boundary_node=True + ) assert m.num_fault_ids == 5 - assert m.edges() == [(0, None, {'error_probability': -1.0, 'fault_ids': {1, 2, 4}, 'weight': 1.0}), - (0, 1, {'error_probability': -1.0, 'fault_ids': {1, 4}, 'weight': 1.0}), - (1, 2, {'error_probability': -1.0, 'fault_ids': {0, 1, 2, 3}, 'weight': 1.0})] + assert m.edges() == [ + (0, None, {"error_probability": -1.0, "fault_ids": {1, 2, 4}, "weight": 1.0}), + (0, 1, {"error_probability": -1.0, "fault_ids": {1, 4}, "weight": 1.0}), + (1, 2, {"error_probability": -1.0, "fault_ids": {0, 1, 2, 3}, "weight": 1.0}), + ] def test_no_check_matrix_raises_value_error(): @@ -295,17 +409,21 @@ def test_no_check_matrix_raises_value_error(): def test_from_empty_check_matrix(): - m = Matching.from_check_matrix([[0, 0, 0], [0, 0, 0]], faults_matrix=[[0, 0, 0], [1, 0, 1], [1, 1, 0]]) + m = Matching.from_check_matrix( + [[0, 0, 0], [0, 0, 0]], faults_matrix=[[0, 0, 0], [1, 0, 1], [1, 1, 0]] + ) assert m.num_fault_ids == 3 assert m.num_edges == 0 assert m.num_detectors == 2 def test_type_error_from_check_matrix(): - with pytest.raises(TypeError): + with pytest.raises((TypeError, ValueError)): Matching.from_check_matrix([[0, 1, 1]], faults_matrix=["A", 1]) def test_spacelike_weights_and_weights_raises_value_error(): with pytest.raises(ValueError): - Matching.from_check_matrix([[0, 1, 1]], weights=[1, 1, 1], spacelike_weights=[2, 2, 2]) + Matching.from_check_matrix( + [[0, 1, 1]], weights=[1, 1, 1], spacelike_weights=[2, 2, 2] + )