diff --git a/release/setup.py b/release/setup.py index 71fd75f6f..5d6a67b2a 100644 --- a/release/setup.py +++ b/release/setup.py @@ -51,8 +51,9 @@ def finalize_options(self): REQUIRED_PACKAGES = [ - 'cirq == 0.11.0', 'sympy == 1.8', 'googleapis-common-protos==1.52.0', - 'google-api-core==1.21.0', 'google-auth==1.18.0', 'protobuf==3.13.0' + 'cirq-core==0.13.1', 'cirq-google==0.13.1', 'sympy == 1.8', + 'googleapis-common-protos==1.52.0', 'google-api-core==1.21.0', + 'google-auth==1.18.0', 'protobuf==3.17.3' ] # placed as extra to not have required overwrite existing nightly installs if diff --git a/requirements.txt b/requirements.txt index 9e8569b7e..98b3997fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -cirq==0.11.0 +cirq-core==0.13.1 +cirq-google==0.13.1 sympy==1.8 numpy==1.19.5 # TensorFlow can detect if it was built against other versions. nbconvert==5.6.1 @@ -12,4 +13,4 @@ google-api-core==1.21.0 google-auth==1.18.0 google-api-python-client==1.8.0 grpcio==1.34.1 -protobuf==3.13.0 \ No newline at end of file +protobuf==3.17.3 \ No newline at end of file diff --git a/tensorflow_quantum/core/ops/tfq_utility_ops_test.py b/tensorflow_quantum/core/ops/tfq_utility_ops_test.py index 013dd1c6c..10d91298d 100644 --- a/tensorflow_quantum/core/ops/tfq_utility_ops_test.py +++ b/tensorflow_quantum/core/ops/tfq_utility_ops_test.py @@ -117,8 +117,11 @@ def test_append_circuit(self, max_n_bits, symbols, n_circuits): cirq_results = [ a + b for a, b in zip(base_circuits, circuits_to_append) ] - self.assertAllEqual(util.convert_to_tensor(tfq_results), - util.convert_to_tensor(cirq_results)) + self.assertAllEqual( + util.convert_to_tensor(tfq_results, + deterministic_proto_serialize=True), + util.convert_to_tensor(cirq_results, + deterministic_proto_serialize=True)) @parameterized.parameters([{ 'padded_array': [[[1, 0, 0, 0], [1, 1, 1, 1]], diff --git a/tensorflow_quantum/core/serialize/serializer.py b/tensorflow_quantum/core/serialize/serializer.py index 41a54228f..9bd8512cf 100644 --- a/tensorflow_quantum/core/serialize/serializer.py +++ b/tensorflow_quantum/core/serialize/serializer.py @@ -144,8 +144,16 @@ def _qid_shape_(self): # pylint: disable=invalid-name def on(self, *qubits): """Returns gate_callable on qubits controlled by contol_qubits.""" - return self._gate_callable(*qubits).controlled_by( - *self._control_qubits, control_values=self._control_values) + gate = self._gate_callable(*qubits) + # TODO(tonybruguier,#636): Here we call the parent's class controlled_by + # because Cirq's breaking change #4167 created 3-qubit gates that cannot + # be serialized yet. Instead, support 3-qubit gates and revert the + # work-around. + if len(self._control_qubits) == 0: + return gate + return cirq.ControlledOperation(self._control_qubits, + gate, + control_values=self._control_values) # pylint: enable=invalid-name diff --git a/tensorflow_quantum/core/serialize/serializer_test.py b/tensorflow_quantum/core/serialize/serializer_test.py index 73621ac71..4a4feca9f 100644 --- a/tensorflow_quantum/core/serialize/serializer_test.py +++ b/tensorflow_quantum/core/serialize/serializer_test.py @@ -121,7 +121,13 @@ def _make_controlled_circuit(circuit, control_qubits, control_values): for op in moment: new_op = op for qb, v in zip(control_qubits[::-1], control_values[::-1]): - new_op = new_op.controlled_by(qb, control_values=[v]) + # TODO(tonybruguier,#636): Here we call the parent's class + # controlled_by because Cirq's breaking change #4167 created + # 3-qubit gates that cannot be serialized yet. Instead, support + # 3-qubit gates and revert the work-around. + new_op = cirq.ControlledOperation([qb], + new_op, + control_values=[v]) new_circuit += new_op return new_circuit @@ -404,39 +410,44 @@ def _get_valid_pauli_proto_pairs(): def _get_noise_proto_pairs(): q0 = cirq.GridQubit(0, 0) + # NOTE(tonybruguier): All the parameters are powers of 2. This is because + # Python only uses double, which means that Protobufs use double even if the + # field is a float. However, the serialization sometimes goes though C++ and + # thus would use float. Thus, we need to have numbers that are exactly + # representable on a float. Powers of 2 are a convenient subset. pairs = [ # Depolarization. - (cirq.Circuit(cirq.depolarize(p=0.3)(q0)), - _build_op_proto("DP", ['p'], [0.3], ['0_0'])), + (cirq.Circuit(cirq.depolarize(p=0.5)(q0)), + _build_op_proto("DP", ['p'], [0.5], ['0_0'])), # Asymmetric depolarization. (cirq.Circuit( - cirq.asymmetric_depolarize(p_x=0.1, p_y=0.2, p_z=0.3)(q0)), - _build_op_proto("ADP", ['p_x', 'p_y', 'p_z'], [0.1, 0.2, 0.3], + cirq.asymmetric_depolarize(p_x=0.125, p_y=0.25, p_z=0.5)(q0)), + _build_op_proto("ADP", ['p_x', 'p_y', 'p_z'], [0.125, 0.25, 0.5], ['0_0'])), # Generalized Amplitude damp. - (cirq.Circuit(cirq.generalized_amplitude_damp(p=0.1, gamma=0.2)(q0)), - _build_op_proto("GAD", ['p', 'gamma'], [0.1, 0.2], ['0_0'])), + (cirq.Circuit(cirq.generalized_amplitude_damp(p=0.125, gamma=0.25)(q0)), + _build_op_proto("GAD", ['p', 'gamma'], [0.125, 0.25], ['0_0'])), # Amplitude damp. - (cirq.Circuit(cirq.amplitude_damp(gamma=0.1)(q0)), - _build_op_proto("AD", ['gamma'], [0.1], ['0_0'])), + (cirq.Circuit(cirq.amplitude_damp(gamma=0.125)(q0)), + _build_op_proto("AD", ['gamma'], [0.125], ['0_0'])), # Reset. (cirq.Circuit(cirq.reset(q0)), _build_op_proto("RST", [], [], ['0_0'])), # Phase damp. - (cirq.Circuit(cirq.phase_damp(gamma=0.1)(q0)), - _build_op_proto("PD", ['gamma'], [0.1], ['0_0'])), + (cirq.Circuit(cirq.phase_damp(gamma=0.125)(q0)), + _build_op_proto("PD", ['gamma'], [0.125], ['0_0'])), # Phase flip. - (cirq.Circuit(cirq.phase_flip(p=0.1)(q0)), - _build_op_proto("PF", ['p'], [0.1], ['0_0'])), + (cirq.Circuit(cirq.phase_flip(p=0.125)(q0)), + _build_op_proto("PF", ['p'], [0.125], ['0_0'])), # Bit flip. - (cirq.Circuit(cirq.bit_flip(p=0.1)(q0)), - _build_op_proto("BF", ['p'], [0.1], ['0_0'])) + (cirq.Circuit(cirq.bit_flip(p=0.125)(q0)), + _build_op_proto("BF", ['p'], [0.125], ['0_0'])) ] return pairs diff --git a/tensorflow_quantum/python/layers/circuit_construction/elementary_test.py b/tensorflow_quantum/python/layers/circuit_construction/elementary_test.py index afb7650e2..aa05d3039 100644 --- a/tensorflow_quantum/python/layers/circuit_construction/elementary_test.py +++ b/tensorflow_quantum/python/layers/circuit_construction/elementary_test.py @@ -101,16 +101,20 @@ def test_addcircuit_modify(self): circuit_b = cirq.testing.random_circuit(bits, 10, 0.9, util.get_supported_gates()) - expected_append = util.convert_to_tensor([circuit_a + circuit_b]) - expected_prepend = util.convert_to_tensor([circuit_b + circuit_a]) + expected_append = util.convert_to_tensor( + [circuit_a + circuit_b], deterministic_proto_serialize=True) + expected_prepend = util.convert_to_tensor( + [circuit_b + circuit_a], deterministic_proto_serialize=True) append_layer = elementary.AddCircuit() prepend_layer = elementary.AddCircuit() actual_append = util.convert_to_tensor( - util.from_tensor(append_layer(circuit_a, append=circuit_b))) + util.from_tensor(append_layer(circuit_a, append=circuit_b)), + deterministic_proto_serialize=True) actual_prepend = util.convert_to_tensor( - util.from_tensor(prepend_layer(circuit_a, prepend=circuit_b))) + util.from_tensor(prepend_layer(circuit_a, prepend=circuit_b)), + deterministic_proto_serialize=True) self.assertEqual(expected_append.numpy()[0], actual_append.numpy()[0]) self.assertEqual(expected_prepend.numpy()[0], actual_prepend.numpy()[0]) diff --git a/tensorflow_quantum/python/layers/circuit_executors/input_checks.py b/tensorflow_quantum/python/layers/circuit_executors/input_checks.py index c2c1767df..b73fbde26 100644 --- a/tensorflow_quantum/python/layers/circuit_executors/input_checks.py +++ b/tensorflow_quantum/python/layers/circuit_executors/input_checks.py @@ -21,7 +21,10 @@ from tensorflow_quantum.python import util -def expand_circuits(inputs, symbol_names=None, symbol_values=None): +def expand_circuits(inputs, + symbol_names=None, + symbol_values=None, + deterministic_proto_serialize=False): """Function for consistently expanding circuit inputs. Args: @@ -33,6 +36,8 @@ def expand_circuits(inputs, symbol_names=None, symbol_values=None): parameterizing the input circuits. symbol_values: a Python `list`, `tuple`, or `numpy.ndarray` of floating point values, or `tf.Tensor` of dtype `float32`. + deterministic_proto_serialize: Whether to use a deterministic proto + serialization. Returns: inputs: `tf.Tensor` of dtype `string` with shape [batch_size] @@ -80,11 +85,16 @@ def expand_circuits(inputs, symbol_names=None, symbol_values=None): # Ingest and promote circuit. if isinstance(inputs, cirq.Circuit): # process single circuit. - inputs = tf.tile(util.convert_to_tensor([inputs]), [symbol_batch_dim]) + inputs = tf.tile( + util.convert_to_tensor( + [inputs], + deterministic_proto_serialize=deterministic_proto_serialize), + [symbol_batch_dim]) elif isinstance(inputs, (list, tuple, np.ndarray)): # process list of circuits. - inputs = util.convert_to_tensor(inputs) + inputs = util.convert_to_tensor( + inputs, deterministic_proto_serialize=deterministic_proto_serialize) if not tf.is_tensor(inputs): raise TypeError("circuits cannot be parsed with given input:" @@ -100,7 +110,9 @@ def expand_circuits(inputs, symbol_names=None, symbol_values=None): return inputs, symbol_names, symbol_values -def expand_operators(operators=None, circuit_batch_dim=1): +def expand_operators(operators=None, + circuit_batch_dim=1, + deterministic_proto_serialize=False): """Check and expand operators. Args: @@ -112,6 +124,8 @@ def expand_operators(operators=None, circuit_batch_dim=1): or `cirq.PauliSum`s; or pre-converted `tf.Tensor` of `cirq.PauliString`s or `cirq.PauliSum`s. circuit_batch_dim: number of circuits in the final expansion + deterministic_proto_serialize: Whether to use a deterministic proto + serialization. Returns: operators: `tf.Tensor` of dtype `string` with shape [batch_size, n_ops] @@ -136,7 +150,9 @@ def expand_operators(operators=None, circuit_batch_dim=1): # to match the batch size of circuits. operators = [operators] op_needs_tile = True - operators = util.convert_to_tensor(operators) + operators = util.convert_to_tensor( + operators, + deterministic_proto_serialize=deterministic_proto_serialize) if op_needs_tile: # Don't tile up if the user gave a python list that was precisely diff --git a/tensorflow_quantum/python/layers/circuit_executors/input_checks_test.py b/tensorflow_quantum/python/layers/circuit_executors/input_checks_test.py index d7cbc17b9..fccdbb0f4 100644 --- a/tensorflow_quantum/python/layers/circuit_executors/input_checks_test.py +++ b/tensorflow_quantum/python/layers/circuit_executors/input_checks_test.py @@ -34,7 +34,8 @@ def test_expand_circuits_error(self): names_tensor = tf.convert_to_tensor([str(symbol)], dtype=tf.dtypes.string) circuit_tensor = util.convert_to_tensor( - [cirq.Circuit(cirq.H(qubit)**symbol)]) + [cirq.Circuit(cirq.H(qubit)**symbol)], + deterministic_proto_serialize=True) values_tensor = tf.convert_to_tensor([[0.5]], dtype=tf.dtypes.float32) # Bad circuit arg @@ -89,7 +90,8 @@ def test_allowed_cases(self): cirq.X(qubits[1])**names_symbol_list[1]) circuit_list = [circuit_alone for _ in range(3)] circuit_tuple = tuple(circuit_list) - circuit_tensor = util.convert_to_tensor(circuit_list) + circuit_tensor = util.convert_to_tensor( + circuit_list, deterministic_proto_serialize=True) values_list = [[1], [2], [3]] values_tuple = tuple(values_list) values_ndarray = np.array(values_list) @@ -106,7 +108,8 @@ def test_allowed_cases(self): values_list, values_tuple, values_ndarray, values_tensor ]: circuit_test, names_test, values_test = \ - input_checks.expand_circuits(circuit, names, values) + input_checks.expand_circuits(circuit, names, values, \ + deterministic_proto_serialize=True) self.assertAllEqual(circuit_test, circuit_tensor) self.assertAllEqual(names_test, names_tensor) self.assertAllEqual(values_test, values_tensor) @@ -116,7 +119,8 @@ def test_allowed_cases(self): values_tensor = tf.convert_to_tensor([[]] * 3, dtype=tf.dtypes.float32) for circuit in [circuit_list, circuit_tuple, circuit_tensor]: circuit_test, names_test, values_test = \ - input_checks.expand_circuits(circuit) + input_checks.expand_circuits(circuit, \ + deterministic_proto_serialize=True) self.assertAllEqual(circuit_test, circuit_tensor) self.assertAllEqual(names_test, names_tensor) self.assertAllEqual(values_test, values_tensor) @@ -143,13 +147,15 @@ def test_allowed_cases(self): bare_tuple = tuple(bare_list) shaped_list = [[bare_string]] * batch_dim shaped_tuple = tuple(shaped_list) - op_tensor_single = util.convert_to_tensor([[bare_string]]) + op_tensor_single = util.convert_to_tensor( + [[bare_string]], deterministic_proto_serialize=True) op_tensor = tf.tile(op_tensor_single, [batch_dim, 1]) for op in [ bare_string, bare_sum, bare_list, bare_tuple, shaped_list, shaped_tuple, op_tensor ]: - op_test = input_checks.expand_operators(op, batch_dim) + op_test = input_checks.expand_operators( + op, batch_dim, deterministic_proto_serialize=True) self.assertAllEqual(op_test, op_tensor) diff --git a/tensorflow_quantum/python/util.py b/tensorflow_quantum/python/util.py index e65b831c6..92ebeabee 100644 --- a/tensorflow_quantum/python/util.py +++ b/tensorflow_quantum/python/util.py @@ -115,7 +115,10 @@ def _apply_random_control(gate, all_qubits): return gate control_locs = random.sample(open_qubits, n_open) control_values = random.choices([0, 1], k=n_open) - return gate.controlled_by(*control_locs, control_values=control_values) + # TODO(tonybruguier,#636): Here we call the parent's class controlled_by + # because Cirq's breaking change #4167 created 3-qubit gates that cannot be + # serialized yet. Instead, support 3-qubit gates and revert the work-around. + return cirq.ControlledOperation(control_locs, gate, control_values) def random_symbol_circuit(qubits, @@ -258,7 +261,7 @@ def random_pauli_sums(qubits, max_sum_length, n_sums): # There are no native convertible ops inside of this function. @tf.autograph.experimental.do_not_convert -def convert_to_tensor(items_to_convert): +def convert_to_tensor(items_to_convert, deterministic_proto_serialize=False): """Convert lists of tfq supported primitives to tensor representations. Recursively convert a nested lists of `cirq.PauliSum` or `cirq.Circuit` @@ -290,6 +293,8 @@ def convert_to_tensor(items_to_convert): Args: items_to_convert: Python `list` or nested `list` of `cirq.Circuit` or `cirq.Paulisum` objects. Must be recangular. + deterministic_proto_serialize: Whether to use a deterministic + serialization when calling SerializeToString(). Returns: A `tf.Tensor` that represents the input items. @@ -310,12 +315,14 @@ def recur(items_to_convert, curr_type=None): not curr_type == cirq.Circuit: curr_type = cirq.PauliSum tensored_items.append( - serializer.serialize_paulisum(item).SerializeToString()) + serializer.serialize_paulisum(item).SerializeToString( + deterministic=deterministic_proto_serialize)) elif isinstance(item, cirq.Circuit) and\ not curr_type == cirq.PauliSum: curr_type = cirq.Circuit tensored_items.append( - serializer.serialize_circuit(item).SerializeToString()) + serializer.serialize_circuit(item).SerializeToString( + deterministic=deterministic_proto_serialize)) else: raise TypeError("Incompatible item passed into " "convert_to_tensor. Tensor detected type: {}. " diff --git a/tensorflow_quantum/python/util_test.py b/tensorflow_quantum/python/util_test.py index d8dcb2458..c073d786e 100644 --- a/tensorflow_quantum/python/util_test.py +++ b/tensorflow_quantum/python/util_test.py @@ -28,8 +28,10 @@ def _single_to_tensor(item): raise TypeError("Item must be a Circuit or PauliSum. Got {}.".format( type(item))) if isinstance(item, (cirq.PauliSum, cirq.PauliString)): - return serializer.serialize_paulisum(item).SerializeToString() - return serializer.serialize_circuit(item).SerializeToString() + return serializer.serialize_paulisum(item).SerializeToString( + deterministic=True) + return serializer.serialize_circuit(item).SerializeToString( + deterministic=True) def _exponential(theta, op): @@ -79,12 +81,14 @@ def test_convert_to_tensor(self, item): """Test that the convert_to_tensor function works correctly by manually serializing flat and 2-deep nested lists of Circuits and PauliSums.""" nested = [[item, item]] * 2 - nested_actual = util.convert_to_tensor(nested) + nested_actual = util.convert_to_tensor( + nested, deterministic_proto_serialize=True) nested_expected = np.array( [np.array([_single_to_tensor(x) for x in row]) for row in nested]) self.assertAllEqual(nested_actual, nested_expected) flat = [item, item] - flat_actual = util.convert_to_tensor(flat) + flat_actual = util.convert_to_tensor(flat, + deterministic_proto_serialize=True) flat_expected = np.array([_single_to_tensor(x) for x in flat]) self.assertAllEqual(flat_actual, flat_expected) @@ -106,15 +110,18 @@ def test_convert_to_tensor_errors(self): def test_from_tensor(self, item): """Check from_tensor assuming convert_to_tensor works.""" - item_nested_tensorized = util.convert_to_tensor([[item, item], - [item, item]]) - item_flat_tensorized = util.convert_to_tensor([item, item]) + item_nested_tensorized = util.convert_to_tensor( + [[item, item], [item, item]], deterministic_proto_serialize=True) + item_flat_tensorized = util.convert_to_tensor( + [item, item], deterministic_proto_serialize=True) item_nested_cycled = util.convert_to_tensor( - util.from_tensor(item_nested_tensorized)) + util.from_tensor(item_nested_tensorized), + deterministic_proto_serialize=True) self.assertAllEqual(item_nested_tensorized, item_nested_cycled) item_flat_cycled = util.convert_to_tensor( - util.from_tensor(item_flat_tensorized)) + util.from_tensor(item_flat_tensorized), + deterministic_proto_serialize=True) self.assertAllEqual(item_flat_tensorized, item_flat_cycled) def test_from_tensor_errors(self): @@ -570,7 +577,7 @@ def test_serializability(self): op1 = theta * cirq.Z(q[0]) * cirq.Z(q[1]) op2 = theta * identity circuit = util.exponential(operators=[op1, op2]) - util.convert_to_tensor([circuit]) + util.convert_to_tensor([circuit], deterministic_proto_serialize=True) if __name__ == "__main__":