From 382cec8ae8ad64ae306cd8063b6e3f9c092dc8ff Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Wed, 17 Dec 2025 00:24:52 +0000 Subject: [PATCH 01/20] Add Contextuality magic square game implementation --- recirq/contextuality/magic_square_game.py | 756 ++++++++++++++++++++++ 1 file changed, 756 insertions(+) create mode 100644 recirq/contextuality/magic_square_game.py diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py new file mode 100644 index 00000000..424f2ee9 --- /dev/null +++ b/recirq/contextuality/magic_square_game.py @@ -0,0 +1,756 @@ +# Copyright 2025 Google +# +# 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 +# +# https://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. + +"""Runs the MagicSquareGame.""" + +import dataclasses +from typing import Any, Literal + +import cirq +import numpy as np + + +@dataclasses.dataclass +class ContextualityResult: + """Result of the contextuality warm-up experiment. + + Attributes: + alice_measurements: Alice's measurements. Shape is (mermin_row_alice, mermin_col_bob, + repetition, mermin_col). + bob_measurements: Bob's measurements. Shape is (mermin_row_alice, mermin_col_bob, + repetition, mermin_row). + """ + + alice_measurements: np.ndarray + bob_measurements: np.ndarray + + def _select_choices_from_second_data(self) -> tuple[np.ndarray, np.ndarray]: + return self.alice_measurements, self.bob_measurements + + def _generate_choices_from_rules_guess_3rd(self) -> tuple[np.ndarray, np.ndarray]: + """Generate choices from Alice and Bob's measurements by inferring the third number from the + first two. + + Returns: + Alice and Bob's choices in the game. + """ + repetitions = self.alice_measurements.shape[2] + alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) + bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) + + alice_choices[0, :, :, 0] = self.alice_measurements[ + 0, :, :, 1 + ] # TODO: is this swap of 1<->0 a bug? + alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] + alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] + alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] + bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] + bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] + bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] + + alice_choices[:, :, :, 2] = np.sum(alice_choices, axis=3) % 2 + bob_choices[:, :, :, 2] = 1 - (np.sum(bob_choices, axis=3) % 2) + return alice_choices, bob_choices + + def _generate_choices_from_rules_measure_3rd_classical_multiplication( + self, + ) -> tuple[np.ndarray, np.ndarray]: + """Generate choices from Alice and Bob's measurements by measuring + two one-body obserbables and making a classical multiplication to get the result + + Returns: + Alice and Bob's choices in the game. + """ + repetitions = self.alice_measurements.shape[2] + alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) + bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) + + alice_choices[0, :, :, 0] = self.alice_measurements[ + 0, :, :, 1 + ] # TODO: is this swap of 1<->0 a bug? + alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] + alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] + alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] + bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] + bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] + bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] + + alice_choices[:, :, :, 2] = 1 - self.alice_measurements[:, :, :, 2] + + bob_choices[:, :, :, 2] = 1 - self.bob_measurements[:, :, :, 2] + bob_choices[0, 0, :, 2] = self.bob_measurements[0, 0, :, 2] + bob_choices[0, 1, :, 2] = self.bob_measurements[0, 1, :, 2] + bob_choices[1, 0, :, 2] = self.bob_measurements[1, 0, :, 2] + bob_choices[1, 1, :, 2] = self.bob_measurements[1, 1, :, 2] + bob_choices[2, 0, :, 2] = self.bob_measurements[2, 0, :, 2] + bob_choices[2, 1, :, 2] = self.bob_measurements[2, 1, :, 2] + + return alice_choices, bob_choices + + def _generate_choices_from_rules_measure_3rd_quantum_multiplication( + self, + ) -> tuple[np.ndarray, np.ndarray]: + """Generate choices from Alice and Bob's measurements by measuring + one two body operator for the third observable. + + Returns: + Alice and Bob's choices in the game. + """ + repetitions = self.alice_measurements.shape[2] + alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) + bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) + + alice_choices[0, :, :, 0] = self.alice_measurements[ + 0, :, :, 1 + ] # TODO: is this swap of 1<->0 a bug? + alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] + alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] + alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] + bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] + bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] + bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] + + alice_choices[:, :, :, 2] = self.alice_measurements[:, :, :, 2] + bob_choices[:, 0, :, 2] = 1 - self.bob_measurements[:, 0, :, 2] + bob_choices[:, 1, :, 2] = 1 - self.bob_measurements[:, 1, :, 2] + bob_choices[:, 2, :, 2] = self.bob_measurements[:, 2, :, 2] + + return alice_choices, bob_choices + + def generate_choices( + self, + game: Literal[ + "guess_3rd", + "measure_3rd_classical_multiplication", + "measure_3rd_quantum_multiplication", + ], + seed: int | None = None, + ) -> tuple[np.ndarray, np.ndarray]: + """Generate choices from Alice and Bob's measurements. + + Args: + seed: A seed for the random number generator. + game: + guess_3rd means making two measurements and infering the third bit + measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + get three bits out of them by mutiplying two of them toghether. + This corresponds to measure A and B to compute A*B. + measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + Use a two qubit interaction to directly measure A*B. + + Returns: + Alice and Bob's choices in the game. + + Raises: + NotImplementedError: If Alice and Bob measure unequal numbers of Paulis. + """ + # return self._select_choices_from_second_data() + if game == "guess_3rd": + return self._generate_choices_from_rules_guess_3rd() + if game == "measure_3rd_classical_multiplication": + return self._generate_choices_from_rules_measure_3rd_classical_multiplication() + if game == "measure_3rd_quantum_multiplication": + return self._generate_choices_from_rules_measure_3rd_quantum_multiplication() + + def get_win_matrix(self, game, seed: int | None = None) -> np.ndarray: + """Find the fraction of the time that + Alice and Bob "agree" (in the intersection) + given that + they "multiply correctly" (alice to -1 and bob to +1). + + Args: + seed: The seed for the random number generator. + game: + guess_3rd means making two measurements and infering the third bit + measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + get three bits out of them by mutiplying two of them toghether. + This corresponds to measure A and B to compute A*B. + measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + Use a two qubit interaction to directly measure A*B. + + Returns: + The fraction of the time that they "agree" given that they "multiply correctly". + """ + alice_choices, bob_choices = self.generate_choices(game, seed) + win_matrix = np.zeros((3, 3)) + repetitions = alice_choices.shape[2] + print(f"{repetitions=}") + for row in range(3): + for col in range(3): + number_of_matches = ( + 0 # if multiplication rules are not respected, there is no match + ) + for rep in range(repetitions): + alice_triad = alice_choices[row, col, rep, :] + bob_triad = bob_choices[row, col, rep, :] + if np.sum(alice_triad) % 2 == 0 and np.sum(bob_triad) % 2 == 1: + number_of_matches += 1 + if alice_triad[col] == bob_triad[row]: + win_matrix[row, col] += 1 + print("Times they both multi[ly correctly to +-1 =", number_of_matches) + win_matrix[row, col] = ( + win_matrix[row, col] / number_of_matches + ) # TODO: uncomment once done with testing + return win_matrix + + def get_multiply_matrix(self, game, seed: int | None = None) -> np.ndarray: + """Find the fraction of the time that Alice and Bob + "multiply correctly" (alice to -1 and bob to +1). + + Args: + seed: The seed for the random number generator. + game: + guess_3rd means making two measurements and infering the third bit + measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + get three bits out of them by mutiplying two of them toghether. + This corresponds to measure A and B to compute A*B. + measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + Use a two qubit interaction to directly measure A*B. + + Returns: + The fraction of the time that they "multiply correctly". + """ + alice_choices, bob_choices = self.generate_choices(game, seed) + win_matrix = np.zeros((3, 3)) + repetitions = alice_choices.shape[2] + print(f"{repetitions=}") + for row in range(3): + for col in range(3): + for rep in range(repetitions): + alice_triad = alice_choices[row, col, rep, :] + bob_triad = bob_choices[row, col, rep, :] + if np.sum(alice_triad) % 2 == 0 and np.sum(bob_triad) % 2 == 1: + win_matrix[row, col] += 1 + + win_matrix[row, col] = win_matrix[row, col] / repetitions + return win_matrix + + def get_agree_matrix(self, game, seed: int | None = None) -> np.ndarray: + """Find the fraction of the time that Alice and Bob + Alice and Bob "agree"(in the intersection) . + + Args: + seed: The seed for the random number generator. + game: + guess_3rd means making two measurements and infering the third bit + measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + get three bits out of them by mutiplying two of them toghether. + This corresponds to measure A and B to compute A*B. + measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + Use a two qubit interaction to directly measure A*B. + + Returns: + The fraction of the time that they "agree". + """ + alice_choices, bob_choices = self.generate_choices(game, seed) + win_matrix = np.zeros((3, 3)) + repetitions = alice_choices.shape[2] + print(f"{repetitions=}") + for row in range(3): + for col in range(3): + for rep in range(repetitions): + alice_triad = alice_choices[row, col, rep, :] + bob_triad = bob_choices[row, col, rep, :] + if alice_triad[col] == bob_triad[row]: + win_matrix[row, col] += 1 + win_matrix[row, col] = win_matrix[row, col] / repetitions + return win_matrix + + def get_agree_and_multiply_matrix(self, game, seed: int | None = None) -> np.ndarray: + """Find the fraction of the time that Alice and Bob + Alice and Bob "agree" (in the intersection) + and + they "multiply correctly" (alice to -1 and bob to +1). + + Args: + seed: The seed for the random number generator. + game: + guess_3rd means making two measurements and infering the third bit + measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + get three bits out of them by mutiplying two of them toghether. + This corresponds to measure A and B to compute A*B. + measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + Use a two qubit interaction to directly measure A*B. + + Returns: + The fraction of the time that they "multiply correctly" AND "agree". + """ + alice_choices, bob_choices = self.generate_choices(game, seed) + win_matrix = np.zeros((3, 3)) + repetitions = alice_choices.shape[2] + print(f"{repetitions=}") + for row in range(3): + for col in range(3): + for rep in range(repetitions): + alice_triad = alice_choices[row, col, rep, :] + bob_triad = bob_choices[row, col, rep, :] + if ( + alice_triad[col] == bob_triad[row] + and np.sum(alice_triad) % 2 == 0 + and np.sum(bob_triad) % 2 == 1 + ): + win_matrix[row, col] += 1 + win_matrix[row, col] = win_matrix[row, col] / repetitions + return win_matrix + + +def run_contextuality_experiment( + sampler: cirq.Sampler, + alice_qubits: list[cirq.GridQubit], + bob_qubits: list[cirq.GridQubit], + game: Literal[ + "guess_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication" + ], + sub_case: Literal["square_1", "square_2", "only_two_qubits"], + repetitions: int = 10_000, + add_dd: bool = True, + dd_scheme: tuple[cirq.Gate, ...] = (cirq.X, cirq.Y, cirq.X, cirq.Y), +) -> ContextualityResult: + """Run the contextuality warm-up experiment. + + Args: + sampler: The hardware sampler or simulator. + alice_qubits: Alice's qubits, order is (measure, measure, data, data, measure) or (measure, + data, data, measure). + bob_qubits: Bob's qubits, (order is measure, measure, data, data, measure) or (measure, + data, data, measure). + repetitions: The number of repetitions for each row and column of the Mermin-Peres square. + add_dd: Whether to add dynamical decoupling. + dd_scheme: The dynamical decoupling sequence to use if doing DD. + game: + guess_3rd means making two measurements and infering the third bit + measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + get three bits out of them by mutiplying two of them toghether. + This corresponds to measure A and B to compute A*B. + measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + Use a two qubit interaction to directly measure A*B. + sub_case: + square1 is the wikipedia square: https://en.wikipedia.org/wiki/Quantum_pseudo-telepathy + square2 is not implemented, all blocks have 2body observables + only_two_qubits means with 4q total (2 per player). Can only do guess_3rd + + Returns: + A ContextualityResult object containing the experiment results. + """ + + all_circuits = [] + for mermin_row in range(3): + for mermin_col in range(3): + all_circuits.append( + construct_contextuality_circuit( + alice_qubits, + bob_qubits, + mermin_row, + mermin_col, + add_dd, + game, + sub_case, + dd_scheme, + ) + ) + results = sampler.run_batch(all_circuits, repetitions=repetitions) + + alice_measurements = np.zeros((3, 3, repetitions, 3), dtype=bool) + bob_measurements = np.zeros((3, 3, repetitions, 3), dtype=bool) + idx = 0 + + # Useful for classical multiplication + def multiply_bool(bool_0: list[bool] | Any, bool_1: list[bool] | Any): + return [el0 == el1 for el0, el1 in zip(bool_0, bool_1)] + + for row in range(3): + for col in range(3): + if sub_case == "only_two_qubits": + alice_measurements[row, col, :, :2] = results[idx][0].measurements[ + "alice_datas" + ] + + bob_measurements[row, col, :, :2] = results[idx][0].measurements["bob_datas"] + else: + alice_measurements[row, col, :, :2] = results[idx][0].measurements["alice"] + bob_measurements[row, col, :, :2] = results[idx][0].measurements["bob"] + + if game == "measure_3rd_classical_multiplication": + alice_measurements[row, col, :, 2] = multiply_bool( + results[idx][0].measurements["alice_datas"][:, 1], + results[idx][0].measurements["alice_datas"][:, 0], + ) + bob_measurements[row, col, :, 2] = multiply_bool( + results[idx][0].measurements["bob_datas"][:, 1], + results[idx][0].measurements["bob_datas"][:, 0], + ) + if game == "measure_3rd_quantum_multiplication": + alice_measurements[row, col, :, 2] = results[idx][0].measurements[ + "alice_datas" + ][:, 0] + bob_measurements[row, col, :, 2] = results[idx][0].measurements["bob_datas"][ + :, 0 + ] + + idx += 1 + + return ContextualityResult(alice_measurements, bob_measurements) + + +def bell_pair_prep_circuit(q0: cirq.GridQubit, q1: cirq.GridQubit) -> cirq.Circuit: + """Prepare a Bell state between qubits q0 and q1. + + Args: + q0: One qubit. + q1: The other qubit. + + Returns: + A circuit creating a Bell state. + """ + return cirq.Circuit(cirq.H.on_each(q0, q1), cirq.CZ(q0, q1), cirq.H(q1)) + + +def state_prep_circuit( + alice_data_qubits: tuple[cirq.GridQubit, cirq.GridQubit], + bob_data_qubits: tuple[cirq.GridQubit, cirq.GridQubit], +) -> cirq.Circuit: + """Construct a circuit to produce the initial Bell pairs. + + Args: + alice_data_qubits: Alice's data qubits. + bob_data_qubits: Bob's data qubits. + + Returns: + A circuit preparing the Bell states. + """ + pairs = ((alice_data_qubits[0], bob_data_qubits[0]), (alice_data_qubits[1], bob_data_qubits[1])) + return bell_pair_prep_circuit(*pairs[0]).zip(bell_pair_prep_circuit(*pairs[1])) + + +def construct_measure_circuit( + alice_qubits: list[cirq.GridQubit], + bob_qubits: list[cirq.GridQubit], + mermin_row: int, + mermin_col: int, + game: Literal[ + "guess_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication" + ], + sub_case: Literal["square_1", "square_2", "only_two_qubits"], +) -> cirq.Circuit: + """Construct a circuit to implement the measurement. + + We use the conventions described in https://en.wikipedia.org/wiki/Quantum_pseudo-telepathy. + In particular, the Mermin-Peres table is + + I ⊗ Z | Z ⊗ I | Z ⊗ Z + X ⊗ I | I ⊗ X | X ⊗ X + -X ⊗ Z |-Z ⊗ X | Y ⊗ Y + + Args: + alice_qubits: The line of qubits to use for Alice (measure, data, data, measure). + bob_qubits: The line of qubits to use for Bob (measure, data, data, measure). + mermin_row: The row of the Mermin-Peres square to measure. + mermin_col: The column of the Mermin-Peres square to measure. + game: + guess_3rd means making two measurements and infering the third bit + measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + get three bits out of them by mutiplying two of them toghether. + This corresponds to measure A and B to compute A*B. + measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + Use a two qubit interaction to directly measure A*B. + sub_case: + square1 is the wikipedia square: https://en.wikipedia.org/wiki/Quantum_pseudo-telepathy + square2 is not implemented, all blocks have 2body observables + only_two_qubits means with 4q total (2 per player). Can only do guess_3rd + + Returns: + A circuit implementing the measurement. + """ + + q = alice_qubits[1:3] # data qubits + m = (alice_qubits[0], alice_qubits[3]) # measure qubits + if mermin_row == 0: + # print("print me ") + if sub_case == "square_1": + alice_circuit = cirq.Circuit( + # map datas into two measures to measure I ⊗ Z and Z ⊗ I on them + cirq.H.on_each(*m), + cirq.CZ.on_each(*zip(m, q)), + cirq.H.on_each(*m), + # map the two datas onto the second data + cirq.Moment(cirq.M(*m, key="alice")), + ) + + if game == "guess_3rd": + pass + elif game == "measure_3rd_classical_multiplication": + alice_circuit.append(cirq.M(*q, key="alice_datas")) + + elif game == "measure_3rd_quantum_multiplication": + alice_circuit.append([ + cirq.H.on(q[1]), + cirq.CZ.on(*q), + cirq.H.on(q[1]), + cirq.M(q[1], key="alice_datas"), + ]) + + if sub_case == "only_two_qubits": + alice_circuit = cirq.Circuit(cirq.Moment(cirq.M(*q, key="alice_datas"))) + + if game == "guess_3rd": + pass + else: + raise ValueError("You can only game = guess_3rd if you sub_case = only_two_qubits") + + elif mermin_row == 1: + if sub_case == "square_1": + alice_circuit = cirq.Circuit( + cirq.H.on_each(*q, *m), + cirq.CZ.on_each(*zip(q, m)), + cirq.H.on_each(*m), + cirq.Moment(cirq.M(*m, key="alice")), + ) + + if game == "guess_3rd": + pass + + elif game == "measure_3rd_classical_multiplication": + alice_circuit.append(cirq.M(*q, key="alice_datas")) + + elif game == "measure_3rd_quantum_multiplication": + alice_circuit.append([ + cirq.H.on(q[1]), + cirq.CZ.on(*q), + cirq.H.on(q[1]), + cirq.M(q[1], key="alice_datas"), + ]) + + if sub_case == "only_two_qubits": + alice_circuit = cirq.Circuit( + cirq.Moment(cirq.H.on_each(*q)), cirq.Moment(cirq.M(*q, key="alice_datas")) + ) + + if game == "guess_3rd": + pass + else: + raise ValueError("You can only game = guess_3rd if you sub_case = only_two_qubits") + + elif mermin_row == 2: + if sub_case == "square_1": + alice_circuit = cirq.Circuit( + cirq.CZ(*q), + cirq.Moment(cirq.H.on_each(*q, *m)), + cirq.CZ.on_each(*zip(m, q)), + cirq.H.on_each(*m), + cirq.Moment(cirq.H.on_each(*q)), + cirq.CZ(*q), + cirq.Moment(cirq.M(*m, key="alice")), + ) + + if game == "guess_3rd": + pass + elif game == "measure_3rd_classical_multiplication": + alice_circuit.append([ + cirq.Rx(rads=np.pi / 2).on_each(*q), + cirq.M(*q, key="alice_datas"), + ]) + + + elif game == "measure_3rd_quantum_multiplication": + alice_circuit.append([ + cirq.Rx(rads=np.pi / 2).on_each(*q), + cirq.H.on(q[1]), + cirq.CZ.on(*q), + cirq.H.on(q[1]), + cirq.M(q[1], key="alice_datas"), + ]) + + + if sub_case == "only_two_qubits": + alice_circuit = cirq.Circuit( + cirq.ry(np.pi / 2)(q[0]), + cirq.H.on(q[0]), + cirq.CZ.on(*q), + cirq.Moment(cirq.H.on_each(*q)), + cirq.X.on(q[0]), + cirq.Moment(cirq.M(*q, key="alice_datas")), + ) + + if game == "guess_3rd": + pass + else: + raise ValueError("You can only game = guess_3rd if you sub_case = only_two_qubits") + + q = bob_qubits[1:3] # data qubits + m = (bob_qubits[0], bob_qubits[3]) # measure qubits + if mermin_col == 0: + if sub_case == "square_1": + bob_circuit = cirq.Circuit( + # map datas onto measures to measure I ⊗ Z and X ⊗ I on them + cirq.H.on_each(*m, q[0]), + cirq.CZ.on_each(*zip(m, q)), + cirq.H.on_each(*m), + cirq.Moment(cirq.M(*m, key="bob")), + ) + + if game == "guess_3rd": + pass + elif game == "measure_3rd_classical_multiplication": + bob_circuit.append(cirq.M(*q, key="bob_datas")) + + + elif game == "measure_3rd_quantum_multiplication": + bob_circuit.append([ + cirq.H.on(q[1]), + cirq.CZ.on(*q), + cirq.H.on(q[1]), + cirq.M(q[1], key="bob_datas"), + ]) + + if sub_case == "only_two_qubits": + bob_circuit = cirq.Circuit(cirq.H.on(q[0]), cirq.Moment(cirq.M(*q, key="bob_datas"))) + + if game == "guess_3rd": + pass + else: + raise ValueError("You can only game = guess_3rd if you sub_case = only_two_qubits") + + elif mermin_col == 1: + if sub_case == "square_1": + bob_circuit = cirq.Circuit( + # map datas onto measures to measure Z ⊗ I and I ⊗ X on them + cirq.H.on_each(*m, q[1]), + cirq.CZ.on_each(*zip(m, q)), + cirq.H.on_each(*m), + cirq.Moment(cirq.M(*m, key="bob")), + ) + + if game == "guess_3rd": + pass + elif game == "measure_3rd_classical_multiplication": + bob_circuit.append(cirq.M(*q, key="bob_datas")) + + elif game == "measure_3rd_quantum_multiplication": + bob_circuit.append([ + cirq.H.on(q[1]), + cirq.CZ.on(*q), + cirq.H.on(q[1]), + cirq.M(q[1], key="bob_datas"), + ]) + + + if sub_case == "only_two_qubits": + bob_circuit = cirq.Circuit(cirq.H.on(q[1]), cirq.Moment(cirq.M(*q, key="bob_datas"))) + + if game == "guess_3rd": + pass + else: + raise ValueError("You can only game = guess_3rd if you sub_case = only_two_qubits") + + elif mermin_col == 2: + if sub_case == "square_1": + bob_circuit = cirq.Circuit( + cirq.H(q[0]), + cirq.CZ(*q), + cirq.Moment(cirq.H.on_each(*q, *m)), + cirq.CZ.on_each(*zip(m, q)), + cirq.H.on_each(*m), + # re-add Eliott's dropped to circuit + cirq.Moment(cirq.H.on_each(*q)), + cirq.CZ(*q), + cirq.H(q[0]), + cirq.Moment(cirq.M(*m, key="bob")), + ) + + if game == "guess_3rd": + pass + elif game == "measure_3rd_classical_multiplication": + bob_circuit.append([ + cirq.Rx(rads=np.pi / 2).on_each(*q), + cirq.M(*q, key="bob_datas"), + ]) + + elif game == "measure_3rd_quantum_multiplication": + bob_circuit.append([ + cirq.Rx(rads=np.pi / 2).on_each(*q), + cirq.H.on(q[1]), + cirq.CZ.on(*q), + cirq.H.on(q[1]), + cirq.M(q[1], key="bob_datas"), + ]) + + + + if sub_case == "only_two_qubits": + bob_circuit = cirq.Circuit( + cirq.H.on(q[0]), + cirq.CZ.on(*q), + cirq.H.on_each(*q), + cirq.Moment(cirq.M(*q, key="bob_datas")), + ) + + + + if game == "guess_3rd": + pass + else: + raise ValueError("You can only sub_case = only_two_qubits if game = guess_3rd") + return cirq.align_right(alice_circuit + bob_circuit) + +def construct_contextuality_circuit( + alice_qubits: list[cirq.GridQubit], + bob_qubits: list[cirq.GridQubit], + mermin_row: int, + mermin_col: int, + add_dd: bool, + game: Literal[ + "guess_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication" + ], + sub_case: Literal["square_1", "square_2", "only_two_qubits"], + dd_scheme: tuple[cirq.Gate, ...] = (cirq.X, cirq.Y, cirq.X, cirq.Y), +) -> cirq.Circuit: + """Construct a circuit to implement the contextuality experiment on hardware. + + We use the conventions described in https://en.wikipedia.org/wiki/Quantum_pseudo-telepathy. + In particular, the Mermin-Peres table is + + I ⊗ Z | Z ⊗ I | Z ⊗ Z + X ⊗ I | I ⊗ X | X ⊗ X + -X ⊗ Z |-Z ⊗ X | Y ⊗ Y + + Args: + alice_qubits: The line of qubits to use for Alice (measure, data, data, measure). + bob_qubits: The line of qubits to use for Bob (measure, data, data, measure). + mermin_row: The row of the Mermin-Peres square to measure. + mermin_col: The column of the Mermin-Peres square to measure. + add_dd: Whether to add dynamical decoupling. + dd_scheme: The dynamical decoupling sequence to use if doing DD. + game: + guess_3rd means making two measurements and infering the third bit + measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + get three bits out of them by mutiplying two of them toghether. + This corresponds to measure A and B to compute A*B. + measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + Use a two qubit interaction to directly measure A*B. + + Returns: + A circuit implementing the game. + """ + alice_data_qubits = (alice_qubits[1], alice_qubits[2]) + bob_data_qubits = (bob_qubits[1], bob_qubits[2]) + prep_circuit = state_prep_circuit(alice_data_qubits, bob_data_qubits) # test cirq.Circuit() # + measure_circuit = construct_measure_circuit( + alice_qubits, bob_qubits, mermin_row, mermin_col, game, sub_case + ) + circuit = prep_circuit + measure_circuit + if add_dd: + circuit = cirq.add_dynamical_decoupling( + circuit, single_qubit_gate_moments_only=True, schema=dd_scheme + ) + return circuit From c0ae423469b2ccbbeb212fe38bd69458b5817446 Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Wed, 17 Dec 2025 00:31:14 +0000 Subject: [PATCH 02/20] Remove TODOs --- recirq/contextuality/magic_square_game.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index 424f2ee9..94b6d6ad 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -49,9 +49,7 @@ def _generate_choices_from_rules_guess_3rd(self) -> tuple[np.ndarray, np.ndarray alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) - alice_choices[0, :, :, 0] = self.alice_measurements[ - 0, :, :, 1 - ] # TODO: is this swap of 1<->0 a bug? + alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] @@ -76,9 +74,7 @@ def _generate_choices_from_rules_measure_3rd_classical_multiplication( alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) - alice_choices[0, :, :, 0] = self.alice_measurements[ - 0, :, :, 1 - ] # TODO: is this swap of 1<->0 a bug? + alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] @@ -111,9 +107,7 @@ def _generate_choices_from_rules_measure_3rd_quantum_multiplication( alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) - alice_choices[0, :, :, 0] = self.alice_measurements[ - 0, :, :, 1 - ] # TODO: is this swap of 1<->0 a bug? + alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] @@ -188,9 +182,8 @@ def get_win_matrix(self, game, seed: int | None = None) -> np.ndarray: print(f"{repetitions=}") for row in range(3): for col in range(3): - number_of_matches = ( - 0 # if multiplication rules are not respected, there is no match - ) + # if multiplication rules are not respected, there is no match + number_of_matches = 0 for rep in range(repetitions): alice_triad = alice_choices[row, col, rep, :] bob_triad = bob_choices[row, col, rep, :] @@ -201,7 +194,7 @@ def get_win_matrix(self, game, seed: int | None = None) -> np.ndarray: print("Times they both multi[ly correctly to +-1 =", number_of_matches) win_matrix[row, col] = ( win_matrix[row, col] / number_of_matches - ) # TODO: uncomment once done with testing + ) return win_matrix def get_multiply_matrix(self, game, seed: int | None = None) -> np.ndarray: From 33b962dc4645374e1ac32588da5871df28eaf99f Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Fri, 23 Jan 2026 17:58:59 +0000 Subject: [PATCH 03/20] some easy changes after dstrain115 review --- recirq/contextuality/magic_square_game.py | 123 +++++++++++----------- 1 file changed, 61 insertions(+), 62 deletions(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index 94b6d6ad..b39e49f5 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -12,7 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Runs the MagicSquareGame.""" +"""The magic square game is a quantum parity challenge where two players, Alice and Bob, must fill +rows and columns of a 3x3 grid with values of +1 or -1. Alice is assigned a random +row and Bob a random column, and they win if their shared cell matches while Alice’s row product +is +1 and Bob’s column product is -1. While no classical strategy can guarantee a win every +time, players using shared entangled quantum states can win with 100% certainty by exploiting +non-local correlations. The game serves as a "proof" of quantum contextuality, demonstrating that +the outcome of a measurement depends on the other measurements performed alongside it (its context). +In a classical world, each cell in the square would have a fixed, pre-existing value regardless +of whether it's being measured as part of a row or a column. However, the Mermin-Peres game +proves that these values cannot exist independently of their measurement context, +as the algebraic requirements for the rows and columns are mathematically impossible to satisfy +simultaneously with fixed values. +""" import dataclasses from typing import Any, Literal @@ -20,6 +32,12 @@ import cirq import numpy as np +type GameType = Literal[ + "guess_3rd", + "measure_3rd_classical_multiplication", + "measure_3rd_quantum_multiplication", +] + @dataclasses.dataclass class ContextualityResult: @@ -65,7 +83,7 @@ def _generate_choices_from_rules_measure_3rd_classical_multiplication( self, ) -> tuple[np.ndarray, np.ndarray]: """Generate choices from Alice and Bob's measurements by measuring - two one-body obserbables and making a classical multiplication to get the result + two one-body observables and making a classical multiplication to get the result Returns: Alice and Bob's choices in the game. @@ -124,23 +142,17 @@ def _generate_choices_from_rules_measure_3rd_quantum_multiplication( def generate_choices( self, - game: Literal[ - "guess_3rd", - "measure_3rd_classical_multiplication", - "measure_3rd_quantum_multiplication", - ], - seed: int | None = None, + game: GameType, ) -> tuple[np.ndarray, np.ndarray]: """Generate choices from Alice and Bob's measurements. Args: - seed: A seed for the random number generator. game: guess_3rd means making two measurements and infering the third bit - measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. get three bits out of them by mutiplying two of them toghether. This corresponds to measure A and B to compute A*B. - measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. Returns: @@ -157,26 +169,23 @@ def generate_choices( if game == "measure_3rd_quantum_multiplication": return self._generate_choices_from_rules_measure_3rd_quantum_multiplication() - def get_win_matrix(self, game, seed: int | None = None) -> np.ndarray: - """Find the fraction of the time that - Alice and Bob "agree" (in the intersection) - given that + def get_win_matrix(self, game: GameType) -> np.ndarray: + """Find the fraction of the time that Alice and Bob "agree" (in the intersection) given that they "multiply correctly" (alice to -1 and bob to +1). Args: - seed: The seed for the random number generator. game: guess_3rd means making two measurements and infering the third bit - measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. get three bits out of them by mutiplying two of them toghether. This corresponds to measure A and B to compute A*B. - measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. Returns: The fraction of the time that they "agree" given that they "multiply correctly". """ - alice_choices, bob_choices = self.generate_choices(game, seed) + alice_choices, bob_choices = self.generate_choices(game) win_matrix = np.zeros((3, 3)) repetitions = alice_choices.shape[2] print(f"{repetitions=}") @@ -191,30 +200,29 @@ def get_win_matrix(self, game, seed: int | None = None) -> np.ndarray: number_of_matches += 1 if alice_triad[col] == bob_triad[row]: win_matrix[row, col] += 1 - print("Times they both multi[ly correctly to +-1 =", number_of_matches) + print("Times they both multiply correctly to +-1 =", number_of_matches) win_matrix[row, col] = ( win_matrix[row, col] / number_of_matches ) return win_matrix - def get_multiply_matrix(self, game, seed: int | None = None) -> np.ndarray: + def get_multiply_matrix(self, game: GameType) -> np.ndarray: """Find the fraction of the time that Alice and Bob "multiply correctly" (alice to -1 and bob to +1). Args: - seed: The seed for the random number generator. game: guess_3rd means making two measurements and infering the third bit - measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. get three bits out of them by mutiplying two of them toghether. This corresponds to measure A and B to compute A*B. - measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. Returns: The fraction of the time that they "multiply correctly". """ - alice_choices, bob_choices = self.generate_choices(game, seed) + alice_choices, bob_choices = self.generate_choices(game) win_matrix = np.zeros((3, 3)) repetitions = alice_choices.shape[2] print(f"{repetitions=}") @@ -229,24 +237,22 @@ def get_multiply_matrix(self, game, seed: int | None = None) -> np.ndarray: win_matrix[row, col] = win_matrix[row, col] / repetitions return win_matrix - def get_agree_matrix(self, game, seed: int | None = None) -> np.ndarray: - """Find the fraction of the time that Alice and Bob - Alice and Bob "agree"(in the intersection) . + def get_agree_matrix(self, game: GameType) -> np.ndarray: + """Fraction of the time that Alice and Bob Alice and Bob "agree" (in the intersection). Args: - seed: The seed for the random number generator. game: - guess_3rd means making two measurements and infering the third bit - measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + "guess_3rd": making two measurements and infering the third bit + "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. get three bits out of them by mutiplying two of them toghether. This corresponds to measure A and B to compute A*B. - measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. Returns: The fraction of the time that they "agree". """ - alice_choices, bob_choices = self.generate_choices(game, seed) + alice_choices, bob_choices = self.generate_choices(game) win_matrix = np.zeros((3, 3)) repetitions = alice_choices.shape[2] print(f"{repetitions=}") @@ -260,26 +266,25 @@ def get_agree_matrix(self, game, seed: int | None = None) -> np.ndarray: win_matrix[row, col] = win_matrix[row, col] / repetitions return win_matrix - def get_agree_and_multiply_matrix(self, game, seed: int | None = None) -> np.ndarray: + def get_agree_and_multiply_matrix(self, game: GameType) -> np.ndarray: """Find the fraction of the time that Alice and Bob Alice and Bob "agree" (in the intersection) and they "multiply correctly" (alice to -1 and bob to +1). Args: - seed: The seed for the random number generator. game: - guess_3rd means making two measurements and infering the third bit - measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + "guess_3rd": making two measurements and infering the third bit + "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. get three bits out of them by mutiplying two of them toghether. This corresponds to measure A and B to compute A*B. - measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. Returns: The fraction of the time that they "multiply correctly" AND "agree". """ - alice_choices, bob_choices = self.generate_choices(game, seed) + alice_choices, bob_choices = self.generate_choices(game) win_matrix = np.zeros((3, 3)) repetitions = alice_choices.shape[2] print(f"{repetitions=}") @@ -302,9 +307,7 @@ def run_contextuality_experiment( sampler: cirq.Sampler, alice_qubits: list[cirq.GridQubit], bob_qubits: list[cirq.GridQubit], - game: Literal[ - "guess_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication" - ], + game: GameType, sub_case: Literal["square_1", "square_2", "only_two_qubits"], repetitions: int = 10_000, add_dd: bool = True, @@ -322,11 +325,11 @@ def run_contextuality_experiment( add_dd: Whether to add dynamical decoupling. dd_scheme: The dynamical decoupling sequence to use if doing DD. game: - guess_3rd means making two measurements and infering the third bit - measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + "guess_3rd": making two measurements and infering the third bit + "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. get three bits out of them by mutiplying two of them toghether. This corresponds to measure A and B to compute A*B. - measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. sub_case: square1 is the wikipedia square: https://en.wikipedia.org/wiki/Quantum_pseudo-telepathy @@ -358,10 +361,6 @@ def run_contextuality_experiment( bob_measurements = np.zeros((3, 3, repetitions, 3), dtype=bool) idx = 0 - # Useful for classical multiplication - def multiply_bool(bool_0: list[bool] | Any, bool_1: list[bool] | Any): - return [el0 == el1 for el0, el1 in zip(bool_0, bool_1)] - for row in range(3): for col in range(3): if sub_case == "only_two_qubits": @@ -396,6 +395,11 @@ def multiply_bool(bool_0: list[bool] | Any, bool_1: list[bool] | Any): return ContextualityResult(alice_measurements, bob_measurements) +def multiply_bool(bool_0: list[bool], bool_1: list[bool]) -> list[bool]: + """Perform boolean multiplication. Useful for "measure_3rd_classical_multiplication"."""" + return [el0 == el1 for el0, el1 in zip(bool_0, bool_1)] + + def bell_pair_prep_circuit(q0: cirq.GridQubit, q1: cirq.GridQubit) -> cirq.Circuit: """Prepare a Bell state between qubits q0 and q1. @@ -431,9 +435,7 @@ def construct_measure_circuit( bob_qubits: list[cirq.GridQubit], mermin_row: int, mermin_col: int, - game: Literal[ - "guess_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication" - ], + game: GameType, sub_case: Literal["square_1", "square_2", "only_two_qubits"], ) -> cirq.Circuit: """Construct a circuit to implement the measurement. @@ -451,11 +453,11 @@ def construct_measure_circuit( mermin_row: The row of the Mermin-Peres square to measure. mermin_col: The column of the Mermin-Peres square to measure. game: - guess_3rd means making two measurements and infering the third bit - measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + "guess_3rd": making two measurements and infering the third bit + "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. get three bits out of them by mutiplying two of them toghether. This corresponds to measure A and B to compute A*B. - measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. sub_case: square1 is the wikipedia square: https://en.wikipedia.org/wiki/Quantum_pseudo-telepathy @@ -469,7 +471,6 @@ def construct_measure_circuit( q = alice_qubits[1:3] # data qubits m = (alice_qubits[0], alice_qubits[3]) # measure qubits if mermin_row == 0: - # print("print me ") if sub_case == "square_1": alice_circuit = cirq.Circuit( # map datas into two measures to measure I ⊗ Z and Z ⊗ I on them @@ -702,9 +703,7 @@ def construct_contextuality_circuit( mermin_row: int, mermin_col: int, add_dd: bool, - game: Literal[ - "guess_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication" - ], + game: GameType, sub_case: Literal["square_1", "square_2", "only_two_qubits"], dd_scheme: tuple[cirq.Gate, ...] = (cirq.X, cirq.Y, cirq.X, cirq.Y), ) -> cirq.Circuit: @@ -725,11 +724,11 @@ def construct_contextuality_circuit( add_dd: Whether to add dynamical decoupling. dd_scheme: The dynamical decoupling sequence to use if doing DD. game: - guess_3rd means making two measurements and infering the third bit - measure_3rd_classical_multiplication: make 4 measurements for Alice and 4 for Bob. + "guess_3rd": making two measurements and infering the third bit + "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. get three bits out of them by mutiplying two of them toghether. This corresponds to measure A and B to compute A*B. - measure_3rd_quantum_multiplication: make 3 measurements for Alice and 3 for Bob. + "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. Returns: From dd67bb6d1181e60cd4d4f165418b7123d6e328d8 Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Tue, 10 Feb 2026 22:34:43 +0000 Subject: [PATCH 04/20] adding tests --- recirq/contextuality/magic_square_game_test.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 recirq/contextuality/magic_square_game_test.py diff --git a/recirq/contextuality/magic_square_game_test.py b/recirq/contextuality/magic_square_game_test.py new file mode 100644 index 00000000..e69de29b From 8a9c70c1af0c35512d30c435abdb6a95e70c1ef9 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Thu, 12 Feb 2026 02:39:52 +0000 Subject: [PATCH 05/20] Add comments --- recirq/contextuality/magic_square_game.py | 28 ++++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index b39e49f5..b9e7c2df 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -64,19 +64,24 @@ def _generate_choices_from_rules_guess_3rd(self) -> tuple[np.ndarray, np.ndarray Alice and Bob's choices in the game. """ repetitions = self.alice_measurements.shape[2] + + # the following two arrays have indices signifying + # [query_row, query_column, repetition, index_of_output] alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) - alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] - alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] - alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] - alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] - bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] - bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] - bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] - - alice_choices[:, :, :, 2] = np.sum(alice_choices, axis=3) % 2 - bob_choices[:, :, :, 2] = 1 - (np.sum(bob_choices, axis=3) % 2) + # the following manipulations implment the Mermin-Peres square from the + # docstring of `construct_contextuality_circuit` + alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] # I ⊗ Z + alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] # Z ⊗ I + alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] # X ⊗ I | I ⊗ X + alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] # -X ⊗ Z |-Z ⊗ X + bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] # I ⊗ Z + bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] # X ⊗ I + bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] # Z ⊗ I | I ⊗ X + + alice_choices[:, :, :, 2] = np.sum(alice_choices, axis=3) % 2 # infer from rule + bob_choices[:, :, :, 2] = 1 - (np.sum(bob_choices, axis=3) % 2) # infer from rule return alice_choices, bob_choices def _generate_choices_from_rules_measure_3rd_classical_multiplication( @@ -156,7 +161,8 @@ def generate_choices( Use a two qubit interaction to directly measure A*B. Returns: - Alice and Bob's choices in the game. + Alice and Bob's choices in the game. The two numpy arrays have indices + signifying [query_row, query_column, repetition, index_of_output]. Raises: NotImplementedError: If Alice and Bob measure unequal numbers of Paulis. From 839e735740d3b71aed492f9112f979aee5937e0a Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Thu, 12 Feb 2026 21:15:22 +0000 Subject: [PATCH 06/20] update docstring --- recirq/contextuality/magic_square_game.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index b9e7c2df..df76e30b 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -72,16 +72,16 @@ def _generate_choices_from_rules_guess_3rd(self) -> tuple[np.ndarray, np.ndarray # the following manipulations implment the Mermin-Peres square from the # docstring of `construct_contextuality_circuit` - alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] # I ⊗ Z - alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] # Z ⊗ I - alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] # X ⊗ I | I ⊗ X - alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] # -X ⊗ Z |-Z ⊗ X - bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] # I ⊗ Z - bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] # X ⊗ I - bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] # Z ⊗ I | I ⊗ X - - alice_choices[:, :, :, 2] = np.sum(alice_choices, axis=3) % 2 # infer from rule - bob_choices[:, :, :, 2] = 1 - (np.sum(bob_choices, axis=3) % 2) # infer from rule + alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] # I ⊗ Z + alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] # Z ⊗ I + alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] # X ⊗ I | I ⊗ X + alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] # -X ⊗ Z |-Z ⊗ X + bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] # I ⊗ Z + bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] # X ⊗ I + bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] # Z ⊗ I | I ⊗ X + + alice_choices[:, :, :, 2] = np.sum(alice_choices, axis=3) % 2 # infer from rule + bob_choices[:, :, :, 2] = 1 - (np.sum(bob_choices, axis=3) % 2) # infer from rule return alice_choices, bob_choices def _generate_choices_from_rules_measure_3rd_classical_multiplication( From 05f2fa32b972eec95e3ddab121934d0131306d7e Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Fri, 20 Feb 2026 19:35:30 +0000 Subject: [PATCH 07/20] Literals and some of Eliott's docstrings --- recirq/contextuality/magic_square_game.py | 44 ++++++++++------- .../contextuality/magic_square_game_test.py | 49 +++++++++++++++++++ 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index df76e30b..61e34f6a 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -38,6 +38,8 @@ "measure_3rd_quantum_multiplication", ] +type SubCase = Literal["square_1", "square_2", "only_two_qubits"] + @dataclasses.dataclass class ContextualityResult: @@ -94,16 +96,19 @@ def _generate_choices_from_rules_measure_3rd_classical_multiplication( Alice and Bob's choices in the game. """ repetitions = self.alice_measurements.shape[2] + + # the following two arrays have indices signifying + # [query_row, query_column, repetition, index_of_output] alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) - alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] - alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] - alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] - alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] - bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] - bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] - bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] + alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] # I ⊗ Z + alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] # Z ⊗ I + alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] # X ⊗ I | I ⊗ X + alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] # -X ⊗ Z |-Z ⊗ X + bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] # I ⊗ Z + bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] # X ⊗ I + bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] # Z ⊗ I | I ⊗ X alice_choices[:, :, :, 2] = 1 - self.alice_measurements[:, :, :, 2] @@ -127,16 +132,19 @@ def _generate_choices_from_rules_measure_3rd_quantum_multiplication( Alice and Bob's choices in the game. """ repetitions = self.alice_measurements.shape[2] + + # the following two arrays have indices signifying + # [query_row, query_column, repetition, index_of_output] alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) - alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] - alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] - alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] - alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] - bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] - bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] - bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] + alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] # I ⊗ Z + alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] # Z ⊗ I + alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] # X ⊗ I | I ⊗ X + alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] # -X ⊗ Z |-Z ⊗ X + bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] # I ⊗ Z + bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] # X ⊗ I + bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] # Z ⊗ I | I ⊗ X alice_choices[:, :, :, 2] = self.alice_measurements[:, :, :, 2] bob_choices[:, 0, :, 2] = 1 - self.bob_measurements[:, 0, :, 2] @@ -314,7 +322,7 @@ def run_contextuality_experiment( alice_qubits: list[cirq.GridQubit], bob_qubits: list[cirq.GridQubit], game: GameType, - sub_case: Literal["square_1", "square_2", "only_two_qubits"], + sub_case: SubCase, repetitions: int = 10_000, add_dd: bool = True, dd_scheme: tuple[cirq.Gate, ...] = (cirq.X, cirq.Y, cirq.X, cirq.Y), @@ -402,7 +410,7 @@ def run_contextuality_experiment( def multiply_bool(bool_0: list[bool], bool_1: list[bool]) -> list[bool]: - """Perform boolean multiplication. Useful for "measure_3rd_classical_multiplication"."""" + """Perform boolean multiplication. Useful for "measure_3rd_classical_multiplication".""" return [el0 == el1 for el0, el1 in zip(bool_0, bool_1)] @@ -442,7 +450,7 @@ def construct_measure_circuit( mermin_row: int, mermin_col: int, game: GameType, - sub_case: Literal["square_1", "square_2", "only_two_qubits"], + sub_case: SubCase, ) -> cirq.Circuit: """Construct a circuit to implement the measurement. @@ -710,7 +718,7 @@ def construct_contextuality_circuit( mermin_col: int, add_dd: bool, game: GameType, - sub_case: Literal["square_1", "square_2", "only_two_qubits"], + sub_case: SubCase, dd_scheme: tuple[cirq.Gate, ...] = (cirq.X, cirq.Y, cirq.X, cirq.Y), ) -> cirq.Circuit: """Construct a circuit to implement the contextuality experiment on hardware. diff --git a/recirq/contextuality/magic_square_game_test.py b/recirq/contextuality/magic_square_game_test.py index e69de29b..b16639ec 100644 --- a/recirq/contextuality/magic_square_game_test.py +++ b/recirq/contextuality/magic_square_game_test.py @@ -0,0 +1,49 @@ +import cirq +import numpy as np +import pytest + +import recirq.contextuality.magic_square_game as msg + + +@pytest.mark.parametrize( + "game", + ["guess_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication"], +) +@pytest.mark.parametrize("add_dd", [True, False]) +def test_run_contextuality_experiment(game: msg.GameType, add_dd: bool) -> None: + """Test that Alice and Bob win 100% of the time with a noiseless simulator.""" + + sampler = cirq.Simulator() + alice_qubits = cirq.GridQubit.rect(1, 4, 0, 0) # test with 2 measure qubits + bob_qubits = cirq.GridQubit.rect(1, 4, 1, 0) + result = msg.run_contextuality_experiment( + sampler, + alice_qubits, + bob_qubits, + game=game, + add_dd=add_dd, + sub_case="square_1", + repetitions=10, + ) + assert np.all(result.get_win_matrix(game) == np.ones((3, 3))) + + +@pytest.mark.parametrize( + "game", + ["guess_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication"], +) +@pytest.mark.parametrize("add_dd", [True, False]) +def test_invalid_input_raises(game: msg.GameType, add_dd: bool): + sampler = cirq.Simulator() + alice_qubits = cirq.GridQubit.rect(1, 4, 0, 0) # test with 2 measure qubits + bob_qubits = cirq.GridQubit.rect(1, 4, 1, 0) + with pytest.raises(ValueError, match="guess_3rd if you sub_case = only_two_qubits"): + _ = msg.run_contextuality_experiment( + sampler, + alice_qubits, + bob_qubits, + game=game, + add_dd=add_dd, + sub_case=sub_case, + repetitions=10, + ) From 194efd9dd279ad174e69c3a4061485c9d69cebfc Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Fri, 20 Feb 2026 19:39:42 +0000 Subject: [PATCH 08/20] typos --- recirq/contextuality/magic_square_game.py | 80 +++++++++++------------ 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index 61e34f6a..ecb1ab9c 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -33,7 +33,7 @@ import numpy as np type GameType = Literal[ - "guess_3rd", + "infer_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication", ] @@ -58,7 +58,7 @@ class ContextualityResult: def _select_choices_from_second_data(self) -> tuple[np.ndarray, np.ndarray]: return self.alice_measurements, self.bob_measurements - def _generate_choices_from_rules_guess_3rd(self) -> tuple[np.ndarray, np.ndarray]: + def _generate_choices_from_rules_infer_3rd(self) -> tuple[np.ndarray, np.ndarray]: """Generate choices from Alice and Bob's measurements by inferring the third number from the first two. @@ -161,9 +161,9 @@ def generate_choices( Args: game: - guess_3rd means making two measurements and infering the third bit + infer_3rd means making two measurements and inferring the third bit "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. - get three bits out of them by mutiplying two of them toghether. + get three bits out of them by multiplying two of them together. This corresponds to measure A and B to compute A*B. "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. @@ -176,8 +176,8 @@ def generate_choices( NotImplementedError: If Alice and Bob measure unequal numbers of Paulis. """ # return self._select_choices_from_second_data() - if game == "guess_3rd": - return self._generate_choices_from_rules_guess_3rd() + if game == "infer_3rd": + return self._generate_choices_from_rules_infer_3rd() if game == "measure_3rd_classical_multiplication": return self._generate_choices_from_rules_measure_3rd_classical_multiplication() if game == "measure_3rd_quantum_multiplication": @@ -189,9 +189,9 @@ def get_win_matrix(self, game: GameType) -> np.ndarray: Args: game: - guess_3rd means making two measurements and infering the third bit + infer_3rd means making two measurements and inferring the third bit "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. - get three bits out of them by mutiplying two of them toghether. + get three bits out of them by multiplying two of them together. This corresponds to measure A and B to compute A*B. "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. @@ -226,9 +226,9 @@ def get_multiply_matrix(self, game: GameType) -> np.ndarray: Args: game: - guess_3rd means making two measurements and infering the third bit + infer_3rd means making two measurements and inferring the third bit "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. - get three bits out of them by mutiplying two of them toghether. + get three bits out of them by multiplying two of them together. This corresponds to measure A and B to compute A*B. "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. @@ -256,9 +256,9 @@ def get_agree_matrix(self, game: GameType) -> np.ndarray: Args: game: - "guess_3rd": making two measurements and infering the third bit + "infer_3rd": making two measurements and inferring the third bit "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. - get three bits out of them by mutiplying two of them toghether. + get three bits out of them by multiplying two of them together. This corresponds to measure A and B to compute A*B. "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. @@ -288,9 +288,9 @@ def get_agree_and_multiply_matrix(self, game: GameType) -> np.ndarray: Args: game: - "guess_3rd": making two measurements and infering the third bit + "infer_3rd": making two measurements and inferring the third bit "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. - get three bits out of them by mutiplying two of them toghether. + get three bits out of them by multiplying two of them together. This corresponds to measure A and B to compute A*B. "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. @@ -339,16 +339,16 @@ def run_contextuality_experiment( add_dd: Whether to add dynamical decoupling. dd_scheme: The dynamical decoupling sequence to use if doing DD. game: - "guess_3rd": making two measurements and infering the third bit + "infer_3rd": making two measurements and inferring the third bit "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. - get three bits out of them by mutiplying two of them toghether. + get three bits out of them by multiplying two of them together. This corresponds to measure A and B to compute A*B. "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. sub_case: square1 is the wikipedia square: https://en.wikipedia.org/wiki/Quantum_pseudo-telepathy square2 is not implemented, all blocks have 2body observables - only_two_qubits means with 4q total (2 per player). Can only do guess_3rd + only_two_qubits means with 4q total (2 per player). Can only do infer_3rd Returns: A ContextualityResult object containing the experiment results. @@ -467,16 +467,16 @@ def construct_measure_circuit( mermin_row: The row of the Mermin-Peres square to measure. mermin_col: The column of the Mermin-Peres square to measure. game: - "guess_3rd": making two measurements and infering the third bit + "infer_3rd": making two measurements and inferring the third bit "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. - get three bits out of them by mutiplying two of them toghether. + get three bits out of them by multiplying two of them together. This corresponds to measure A and B to compute A*B. "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. sub_case: square1 is the wikipedia square: https://en.wikipedia.org/wiki/Quantum_pseudo-telepathy square2 is not implemented, all blocks have 2body observables - only_two_qubits means with 4q total (2 per player). Can only do guess_3rd + only_two_qubits means with 4q total (2 per player). Can only do infer_3rd Returns: A circuit implementing the measurement. @@ -495,7 +495,7 @@ def construct_measure_circuit( cirq.Moment(cirq.M(*m, key="alice")), ) - if game == "guess_3rd": + if game == "infer_3rd": pass elif game == "measure_3rd_classical_multiplication": alice_circuit.append(cirq.M(*q, key="alice_datas")) @@ -511,10 +511,10 @@ def construct_measure_circuit( if sub_case == "only_two_qubits": alice_circuit = cirq.Circuit(cirq.Moment(cirq.M(*q, key="alice_datas"))) - if game == "guess_3rd": + if game == "infer_3rd": pass else: - raise ValueError("You can only game = guess_3rd if you sub_case = only_two_qubits") + raise ValueError("You can only game = infer_3rd if you sub_case = only_two_qubits") elif mermin_row == 1: if sub_case == "square_1": @@ -525,7 +525,7 @@ def construct_measure_circuit( cirq.Moment(cirq.M(*m, key="alice")), ) - if game == "guess_3rd": + if game == "infer_3rd": pass elif game == "measure_3rd_classical_multiplication": @@ -544,10 +544,10 @@ def construct_measure_circuit( cirq.Moment(cirq.H.on_each(*q)), cirq.Moment(cirq.M(*q, key="alice_datas")) ) - if game == "guess_3rd": + if game == "infer_3rd": pass else: - raise ValueError("You can only game = guess_3rd if you sub_case = only_two_qubits") + raise ValueError("You can only game = infer_3rd if you sub_case = only_two_qubits") elif mermin_row == 2: if sub_case == "square_1": @@ -561,7 +561,7 @@ def construct_measure_circuit( cirq.Moment(cirq.M(*m, key="alice")), ) - if game == "guess_3rd": + if game == "infer_3rd": pass elif game == "measure_3rd_classical_multiplication": alice_circuit.append([ @@ -590,10 +590,10 @@ def construct_measure_circuit( cirq.Moment(cirq.M(*q, key="alice_datas")), ) - if game == "guess_3rd": + if game == "infer_3rd": pass else: - raise ValueError("You can only game = guess_3rd if you sub_case = only_two_qubits") + raise ValueError("You can only game = infer_3rd if you sub_case = only_two_qubits") q = bob_qubits[1:3] # data qubits m = (bob_qubits[0], bob_qubits[3]) # measure qubits @@ -607,7 +607,7 @@ def construct_measure_circuit( cirq.Moment(cirq.M(*m, key="bob")), ) - if game == "guess_3rd": + if game == "infer_3rd": pass elif game == "measure_3rd_classical_multiplication": bob_circuit.append(cirq.M(*q, key="bob_datas")) @@ -624,10 +624,10 @@ def construct_measure_circuit( if sub_case == "only_two_qubits": bob_circuit = cirq.Circuit(cirq.H.on(q[0]), cirq.Moment(cirq.M(*q, key="bob_datas"))) - if game == "guess_3rd": + if game == "infer_3rd": pass else: - raise ValueError("You can only game = guess_3rd if you sub_case = only_two_qubits") + raise ValueError("You can only game = infer_3rd if you sub_case = only_two_qubits") elif mermin_col == 1: if sub_case == "square_1": @@ -639,7 +639,7 @@ def construct_measure_circuit( cirq.Moment(cirq.M(*m, key="bob")), ) - if game == "guess_3rd": + if game == "infer_3rd": pass elif game == "measure_3rd_classical_multiplication": bob_circuit.append(cirq.M(*q, key="bob_datas")) @@ -656,10 +656,10 @@ def construct_measure_circuit( if sub_case == "only_two_qubits": bob_circuit = cirq.Circuit(cirq.H.on(q[1]), cirq.Moment(cirq.M(*q, key="bob_datas"))) - if game == "guess_3rd": + if game == "infer_3rd": pass else: - raise ValueError("You can only game = guess_3rd if you sub_case = only_two_qubits") + raise ValueError("You can only game = infer_3rd if you sub_case = only_two_qubits") elif mermin_col == 2: if sub_case == "square_1": @@ -676,7 +676,7 @@ def construct_measure_circuit( cirq.Moment(cirq.M(*m, key="bob")), ) - if game == "guess_3rd": + if game == "infer_3rd": pass elif game == "measure_3rd_classical_multiplication": bob_circuit.append([ @@ -705,10 +705,10 @@ def construct_measure_circuit( - if game == "guess_3rd": + if game == "infer_3rd": pass else: - raise ValueError("You can only sub_case = only_two_qubits if game = guess_3rd") + raise ValueError("You can only sub_case = only_two_qubits if game = infer_3rd") return cirq.align_right(alice_circuit + bob_circuit) def construct_contextuality_circuit( @@ -738,9 +738,9 @@ def construct_contextuality_circuit( add_dd: Whether to add dynamical decoupling. dd_scheme: The dynamical decoupling sequence to use if doing DD. game: - "guess_3rd": making two measurements and infering the third bit + "infer_3rd": making two measurements and inferring the third bit "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. - get three bits out of them by mutiplying two of them toghether. + get three bits out of them by multiplying two of them together. This corresponds to measure A and B to compute A*B. "measure_3rd_quantum_multiplication": make 3 measurements for Alice and 3 for Bob. Use a two qubit interaction to directly measure A*B. From bc2e63005a2d4d620720a3ee1b7f1417aaa92f00 Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Fri, 20 Feb 2026 19:55:23 +0000 Subject: [PATCH 09/20] Refactor _assign_choices_from_measurements --- recirq/contextuality/magic_square_game.py | 55 +++++++---------------- 1 file changed, 15 insertions(+), 40 deletions(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index ecb1ab9c..835266af 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -55,16 +55,9 @@ class ContextualityResult: alice_measurements: np.ndarray bob_measurements: np.ndarray - def _select_choices_from_second_data(self) -> tuple[np.ndarray, np.ndarray]: - return self.alice_measurements, self.bob_measurements + def _assign_choices_from_measurements(self) -> tuple[np.ndarray, np.ndarray]: + """Initialize and assign choices from Alice and Bob's measurements.""" - def _generate_choices_from_rules_infer_3rd(self) -> tuple[np.ndarray, np.ndarray]: - """Generate choices from Alice and Bob's measurements by inferring the third number from the - first two. - - Returns: - Alice and Bob's choices in the game. - """ repetitions = self.alice_measurements.shape[2] # the following two arrays have indices signifying @@ -72,8 +65,6 @@ def _generate_choices_from_rules_infer_3rd(self) -> tuple[np.ndarray, np.ndarray alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) - # the following manipulations implment the Mermin-Peres square from the - # docstring of `construct_contextuality_circuit` alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] # I ⊗ Z alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] # Z ⊗ I alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] # X ⊗ I | I ⊗ X @@ -82,6 +73,17 @@ def _generate_choices_from_rules_infer_3rd(self) -> tuple[np.ndarray, np.ndarray bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] # X ⊗ I bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] # Z ⊗ I | I ⊗ X + return alice_choices, bob_choices + + def _generate_choices_from_rules_infer_3rd(self) -> tuple[np.ndarray, np.ndarray]: + """Generate choices from Alice and Bob's measurements by inferring the third number from the + first two. + + Returns: + Alice and Bob's choices in the game. + """ + alice_choices, bob_choices = self._assign_choices_from_measurements() + alice_choices[:, :, :, 2] = np.sum(alice_choices, axis=3) % 2 # infer from rule bob_choices[:, :, :, 2] = 1 - (np.sum(bob_choices, axis=3) % 2) # infer from rule return alice_choices, bob_choices @@ -95,20 +97,7 @@ def _generate_choices_from_rules_measure_3rd_classical_multiplication( Returns: Alice and Bob's choices in the game. """ - repetitions = self.alice_measurements.shape[2] - - # the following two arrays have indices signifying - # [query_row, query_column, repetition, index_of_output] - alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) - bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) - - alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] # I ⊗ Z - alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] # Z ⊗ I - alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] # X ⊗ I | I ⊗ X - alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] # -X ⊗ Z |-Z ⊗ X - bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] # I ⊗ Z - bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] # X ⊗ I - bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] # Z ⊗ I | I ⊗ X + alice_choices, bob_choices = self._assign_choices_from_measurements() alice_choices[:, :, :, 2] = 1 - self.alice_measurements[:, :, :, 2] @@ -131,20 +120,7 @@ def _generate_choices_from_rules_measure_3rd_quantum_multiplication( Returns: Alice and Bob's choices in the game. """ - repetitions = self.alice_measurements.shape[2] - - # the following two arrays have indices signifying - # [query_row, query_column, repetition, index_of_output] - alice_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) - bob_choices = np.zeros((3, 3, repetitions, 3), dtype=bool) - - alice_choices[0, :, :, 0] = self.alice_measurements[0, :, :, 1] # I ⊗ Z - alice_choices[0, :, :, 1] = self.alice_measurements[0, :, :, 0] # Z ⊗ I - alice_choices[1, :, :, :2] = self.alice_measurements[1, :, :, :2] # X ⊗ I | I ⊗ X - alice_choices[2, :, :, :2] = 1 - self.alice_measurements[2, :, :, :2] # -X ⊗ Z |-Z ⊗ X - bob_choices[:, 0, :, 0] = self.bob_measurements[:, 0, :, 1] # I ⊗ Z - bob_choices[:, 0, :, 1] = self.bob_measurements[:, 0, :, 0] # X ⊗ I - bob_choices[:, 1:, :, :2] = self.bob_measurements[:, 1:, :, :2] # Z ⊗ I | I ⊗ X + alice_choices, bob_choices = self._assign_choices_from_measurements() alice_choices[:, :, :, 2] = self.alice_measurements[:, :, :, 2] bob_choices[:, 0, :, 2] = 1 - self.bob_measurements[:, 0, :, 2] @@ -175,7 +151,6 @@ def generate_choices( Raises: NotImplementedError: If Alice and Bob measure unequal numbers of Paulis. """ - # return self._select_choices_from_second_data() if game == "infer_3rd": return self._generate_choices_from_rules_infer_3rd() if game == "measure_3rd_classical_multiplication": From 049d71cdb686fc550ce022c9ff5ad5fdb627a8e4 Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Fri, 20 Feb 2026 20:00:49 +0000 Subject: [PATCH 10/20] change win matrix nomenclature --- recirq/contextuality/magic_square_game.py | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index 835266af..f0c26636 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -158,7 +158,7 @@ def generate_choices( if game == "measure_3rd_quantum_multiplication": return self._generate_choices_from_rules_measure_3rd_quantum_multiplication() - def get_win_matrix(self, game: GameType) -> np.ndarray: + def get_agree_given_multiply_matrix(self, game: GameType) -> np.ndarray: """Find the fraction of the time that Alice and Bob "agree" (in the intersection) given that they "multiply correctly" (alice to -1 and bob to +1). @@ -175,7 +175,7 @@ def get_win_matrix(self, game: GameType) -> np.ndarray: The fraction of the time that they "agree" given that they "multiply correctly". """ alice_choices, bob_choices = self.generate_choices(game) - win_matrix = np.zeros((3, 3)) + agree_given_multiply_matrix = np.zeros((3, 3)) repetitions = alice_choices.shape[2] print(f"{repetitions=}") for row in range(3): @@ -188,12 +188,12 @@ def get_win_matrix(self, game: GameType) -> np.ndarray: if np.sum(alice_triad) % 2 == 0 and np.sum(bob_triad) % 2 == 1: number_of_matches += 1 if alice_triad[col] == bob_triad[row]: - win_matrix[row, col] += 1 + agree_given_multiply_matrix[row, col] += 1 print("Times they both multiply correctly to +-1 =", number_of_matches) - win_matrix[row, col] = ( - win_matrix[row, col] / number_of_matches + agree_given_multiply_matrix[row, col] = ( + agree_given_multiply_matrix[row, col] / number_of_matches ) - return win_matrix + return agree_given_multiply_matrix def get_multiply_matrix(self, game: GameType) -> np.ndarray: """Find the fraction of the time that Alice and Bob @@ -212,7 +212,7 @@ def get_multiply_matrix(self, game: GameType) -> np.ndarray: The fraction of the time that they "multiply correctly". """ alice_choices, bob_choices = self.generate_choices(game) - win_matrix = np.zeros((3, 3)) + multiply_matrix = np.zeros((3, 3)) repetitions = alice_choices.shape[2] print(f"{repetitions=}") for row in range(3): @@ -221,10 +221,10 @@ def get_multiply_matrix(self, game: GameType) -> np.ndarray: alice_triad = alice_choices[row, col, rep, :] bob_triad = bob_choices[row, col, rep, :] if np.sum(alice_triad) % 2 == 0 and np.sum(bob_triad) % 2 == 1: - win_matrix[row, col] += 1 + multiply_matrix[row, col] += 1 - win_matrix[row, col] = win_matrix[row, col] / repetitions - return win_matrix + multiply_matrix[row, col] = multiply_matrix[row, col] / repetitions + return multiply_matrix def get_agree_matrix(self, game: GameType) -> np.ndarray: """Fraction of the time that Alice and Bob Alice and Bob "agree" (in the intersection). @@ -242,7 +242,7 @@ def get_agree_matrix(self, game: GameType) -> np.ndarray: The fraction of the time that they "agree". """ alice_choices, bob_choices = self.generate_choices(game) - win_matrix = np.zeros((3, 3)) + agree_matrix = np.zeros((3, 3)) repetitions = alice_choices.shape[2] print(f"{repetitions=}") for row in range(3): @@ -251,9 +251,9 @@ def get_agree_matrix(self, game: GameType) -> np.ndarray: alice_triad = alice_choices[row, col, rep, :] bob_triad = bob_choices[row, col, rep, :] if alice_triad[col] == bob_triad[row]: - win_matrix[row, col] += 1 - win_matrix[row, col] = win_matrix[row, col] / repetitions - return win_matrix + agree_matrix[row, col] += 1 + agree_matrix[row, col] = agree_matrix[row, col] / repetitions + return agree_matrix def get_agree_and_multiply_matrix(self, game: GameType) -> np.ndarray: """Find the fraction of the time that Alice and Bob @@ -274,7 +274,7 @@ def get_agree_and_multiply_matrix(self, game: GameType) -> np.ndarray: The fraction of the time that they "multiply correctly" AND "agree". """ alice_choices, bob_choices = self.generate_choices(game) - win_matrix = np.zeros((3, 3)) + agree_and_multiply_matrix = np.zeros((3, 3)) repetitions = alice_choices.shape[2] print(f"{repetitions=}") for row in range(3): @@ -287,9 +287,9 @@ def get_agree_and_multiply_matrix(self, game: GameType) -> np.ndarray: and np.sum(alice_triad) % 2 == 0 and np.sum(bob_triad) % 2 == 1 ): - win_matrix[row, col] += 1 - win_matrix[row, col] = win_matrix[row, col] / repetitions - return win_matrix + agree_and_multiply_matrix[row, col] += 1 + agree_and_multiply_matrix[row, col] = agree_and_multiply_matrix[row, col] / repetitions + return agree_and_multiply_matrix def run_contextuality_experiment( From ca8c490067303ad009cffbdd5a7ad4f499fdb96b Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Fri, 20 Feb 2026 22:06:07 +0000 Subject: [PATCH 11/20] tests --- recirq/contextuality/magic_square_game_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/recirq/contextuality/magic_square_game_test.py b/recirq/contextuality/magic_square_game_test.py index b16639ec..ce2d922c 100644 --- a/recirq/contextuality/magic_square_game_test.py +++ b/recirq/contextuality/magic_square_game_test.py @@ -25,7 +25,7 @@ def test_run_contextuality_experiment(game: msg.GameType, add_dd: bool) -> None: sub_case="square_1", repetitions=10, ) - assert np.all(result.get_win_matrix(game) == np.ones((3, 3))) + assert np.all(result.get_agree_given_multiply_matrix(game) == np.ones((3, 3))) @pytest.mark.parametrize( @@ -44,6 +44,6 @@ def test_invalid_input_raises(game: msg.GameType, add_dd: bool): bob_qubits, game=game, add_dd=add_dd, - sub_case=sub_case, + sub_case="square_1", repetitions=10, ) From 7c2621017e1882ca09f6471f242c193168f392ec Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Fri, 20 Feb 2026 22:08:23 +0000 Subject: [PATCH 12/20] SyntaxError --- recirq/contextuality/magic_square_game.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index f0c26636..c2ee6959 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -32,13 +32,13 @@ import cirq import numpy as np -type GameType = Literal[ +GameType = Literal[ "infer_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication", ] -type SubCase = Literal["square_1", "square_2", "only_two_qubits"] +SubCase = Literal["square_1", "square_2", "only_two_qubits"] @dataclasses.dataclass From 1cc608339a9562a5df357cf3a2402d35079e7ffb Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Fri, 20 Feb 2026 23:03:56 +0000 Subject: [PATCH 13/20] solving errors? --- recirq/contextuality/magic_square_game_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/recirq/contextuality/magic_square_game_test.py b/recirq/contextuality/magic_square_game_test.py index ce2d922c..1c098e9f 100644 --- a/recirq/contextuality/magic_square_game_test.py +++ b/recirq/contextuality/magic_square_game_test.py @@ -7,7 +7,7 @@ @pytest.mark.parametrize( "game", - ["guess_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication"], + ["infer_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication"], ) @pytest.mark.parametrize("add_dd", [True, False]) def test_run_contextuality_experiment(game: msg.GameType, add_dd: bool) -> None: @@ -30,20 +30,20 @@ def test_run_contextuality_experiment(game: msg.GameType, add_dd: bool) -> None: @pytest.mark.parametrize( "game", - ["guess_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication"], + ["infer_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication"], ) @pytest.mark.parametrize("add_dd", [True, False]) def test_invalid_input_raises(game: msg.GameType, add_dd: bool): sampler = cirq.Simulator() alice_qubits = cirq.GridQubit.rect(1, 4, 0, 0) # test with 2 measure qubits bob_qubits = cirq.GridQubit.rect(1, 4, 1, 0) - with pytest.raises(ValueError, match="guess_3rd if you sub_case = only_two_qubits"): + with pytest.raises(ValueError, match="infer_3rd if you sub_case = only_two_qubits"): _ = msg.run_contextuality_experiment( sampler, alice_qubits, bob_qubits, game=game, add_dd=add_dd, - sub_case="square_1", + sub_case="only_two_qubits", repetitions=10, ) From aefec2600958b5963c8592fad00c0bcd85b52a38 Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Tue, 24 Feb 2026 23:46:55 +0000 Subject: [PATCH 14/20] silly docs --- recirq/contextuality/magic_square_game.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index c2ee6959..13e135ec 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -145,8 +145,8 @@ def generate_choices( Use a two qubit interaction to directly measure A*B. Returns: - Alice and Bob's choices in the game. The two numpy arrays have indices - signifying [query_row, query_column, repetition, index_of_output]. + Alice and Bob's choices in the game. The two numpy arrays have indices signifying + [query_row, query_column, repetition, index_of_output]. Raises: NotImplementedError: If Alice and Bob measure unequal numbers of Paulis. From dbeaa9db788577b9c75be6d7f0f94014fea4ca84 Mon Sep 17 00:00:00 2001 From: Alejo Gradau Date: Fri, 27 Feb 2026 18:26:56 +0000 Subject: [PATCH 15/20] tests --- .../contextuality/magic_square_game_test.py | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/recirq/contextuality/magic_square_game_test.py b/recirq/contextuality/magic_square_game_test.py index 1c098e9f..fbfcc0eb 100644 --- a/recirq/contextuality/magic_square_game_test.py +++ b/recirq/contextuality/magic_square_game_test.py @@ -26,24 +26,3 @@ def test_run_contextuality_experiment(game: msg.GameType, add_dd: bool) -> None: repetitions=10, ) assert np.all(result.get_agree_given_multiply_matrix(game) == np.ones((3, 3))) - - -@pytest.mark.parametrize( - "game", - ["infer_3rd", "measure_3rd_classical_multiplication", "measure_3rd_quantum_multiplication"], -) -@pytest.mark.parametrize("add_dd", [True, False]) -def test_invalid_input_raises(game: msg.GameType, add_dd: bool): - sampler = cirq.Simulator() - alice_qubits = cirq.GridQubit.rect(1, 4, 0, 0) # test with 2 measure qubits - bob_qubits = cirq.GridQubit.rect(1, 4, 1, 0) - with pytest.raises(ValueError, match="infer_3rd if you sub_case = only_two_qubits"): - _ = msg.run_contextuality_experiment( - sampler, - alice_qubits, - bob_qubits, - game=game, - add_dd=add_dd, - sub_case="only_two_qubits", - repetitions=10, - ) From 88dcc65c2651006d290b63fe589354c9109a71a8 Mon Sep 17 00:00:00 2001 From: alejogoogle <93100640+alejogoogle@users.noreply.github.com> Date: Mon, 23 Mar 2026 11:17:26 -0700 Subject: [PATCH 16/20] Update recirq/contextuality/magic_square_game.py Co-authored-by: Michael Hucka --- recirq/contextuality/magic_square_game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index 13e135ec..a24f15f3 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -263,7 +263,7 @@ def get_agree_and_multiply_matrix(self, game: GameType) -> np.ndarray: Args: game: - "infer_3rd": making two measurements and inferring the third bit + "infer_3rd": make two measurements and infer the third bit. "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. get three bits out of them by multiplying two of them together. This corresponds to measure A and B to compute A*B. From 2c886cff404447961407b2ef83ffbc7fc6a4a486 Mon Sep 17 00:00:00 2001 From: alejogoogle <93100640+alejogoogle@users.noreply.github.com> Date: Mon, 23 Mar 2026 11:17:38 -0700 Subject: [PATCH 17/20] Update recirq/contextuality/magic_square_game.py Co-authored-by: Michael Hucka --- recirq/contextuality/magic_square_game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index a24f15f3..0d781d34 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -314,7 +314,7 @@ def run_contextuality_experiment( add_dd: Whether to add dynamical decoupling. dd_scheme: The dynamical decoupling sequence to use if doing DD. game: - "infer_3rd": making two measurements and inferring the third bit + "infer_3rd": make two measurements and infer the third bit. "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. get three bits out of them by multiplying two of them together. This corresponds to measure A and B to compute A*B. From c145c3585f2624d5069ec68eeac393485c26c78e Mon Sep 17 00:00:00 2001 From: alejogoogle <93100640+alejogoogle@users.noreply.github.com> Date: Mon, 23 Mar 2026 11:18:00 -0700 Subject: [PATCH 18/20] Update recirq/contextuality/magic_square_game.py Co-authored-by: Michael Hucka --- recirq/contextuality/magic_square_game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index 0d781d34..9e89033f 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -386,7 +386,7 @@ def run_contextuality_experiment( def multiply_bool(bool_0: list[bool], bool_1: list[bool]) -> list[bool]: """Perform boolean multiplication. Useful for "measure_3rd_classical_multiplication".""" - return [el0 == el1 for el0, el1 in zip(bool_0, bool_1)] + return bool_0 == bool_1 def bell_pair_prep_circuit(q0: cirq.GridQubit, q1: cirq.GridQubit) -> cirq.Circuit: From 009ad00beec151cf61f62da89ff7cf36eb0abb80 Mon Sep 17 00:00:00 2001 From: alejogoogle <93100640+alejogoogle@users.noreply.github.com> Date: Mon, 23 Mar 2026 11:18:11 -0700 Subject: [PATCH 19/20] Update recirq/contextuality/magic_square_game.py Co-authored-by: Michael Hucka --- recirq/contextuality/magic_square_game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index 9e89033f..14f6247a 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -713,7 +713,7 @@ def construct_contextuality_circuit( add_dd: Whether to add dynamical decoupling. dd_scheme: The dynamical decoupling sequence to use if doing DD. game: - "infer_3rd": making two measurements and inferring the third bit + "infer_3rd": make two measurements and infer the third bit. "measure_3rd_classical_multiplication": make 4 measurements for Alice and 4 for Bob. get three bits out of them by multiplying two of them together. This corresponds to measure A and B to compute A*B. From f4b9516e63b12909f78072f70c738d9e2311dc67 Mon Sep 17 00:00:00 2001 From: alejogoogle <93100640+alejogoogle@users.noreply.github.com> Date: Mon, 23 Mar 2026 11:18:19 -0700 Subject: [PATCH 20/20] Update recirq/contextuality/magic_square_game.py Co-authored-by: Michael Hucka --- recirq/contextuality/magic_square_game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recirq/contextuality/magic_square_game.py b/recirq/contextuality/magic_square_game.py index 14f6247a..08358d64 100644 --- a/recirq/contextuality/magic_square_game.py +++ b/recirq/contextuality/magic_square_game.py @@ -384,7 +384,7 @@ def run_contextuality_experiment( return ContextualityResult(alice_measurements, bob_measurements) -def multiply_bool(bool_0: list[bool], bool_1: list[bool]) -> list[bool]: +def multiply_bool(bool_0: np.ndarray, bool_1: np.ndarray) -> np.ndarray: """Perform boolean multiplication. Useful for "measure_3rd_classical_multiplication".""" return bool_0 == bool_1