From 41d41627e017f8db3e931ece2d755d2c32ea7889 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Fri, 19 Nov 2021 05:22:52 +0000 Subject: [PATCH 01/20] Parsei context with ProjectorSums --- .../core/ops/math_ops/tfq_inner_product.cc | 1 - .../ops/math_ops/tfq_inner_product_grad.cc | 1 - tensorflow_quantum/core/ops/parse_context.cc | 56 ++++++++++++++++++- tensorflow_quantum/core/ops/parse_context.h | 11 +++- .../core/serialize/op_deserializer_test.py | 1 - .../sampled_expectation_test.py | 1 - tensorflow_quantum/python/util.py | 32 +++++++++++ tensorflow_quantum/python/util_test.py | 27 +++++---- 8 files changed, 114 insertions(+), 16 deletions(-) diff --git a/tensorflow_quantum/core/ops/math_ops/tfq_inner_product.cc b/tensorflow_quantum/core/ops/math_ops/tfq_inner_product.cc index 2a66d2919..44183efd2 100644 --- a/tensorflow_quantum/core/ops/math_ops/tfq_inner_product.cc +++ b/tensorflow_quantum/core/ops/math_ops/tfq_inner_product.cc @@ -35,7 +35,6 @@ limitations under the License. namespace tfq { using ::tensorflow::Status; -using ::tfq::proto::PauliSum; using ::tfq::proto::Program; typedef qsim::Cirq::GateCirq QsimGate; diff --git a/tensorflow_quantum/core/ops/math_ops/tfq_inner_product_grad.cc b/tensorflow_quantum/core/ops/math_ops/tfq_inner_product_grad.cc index 5b29571d2..25fafce2b 100644 --- a/tensorflow_quantum/core/ops/math_ops/tfq_inner_product_grad.cc +++ b/tensorflow_quantum/core/ops/math_ops/tfq_inner_product_grad.cc @@ -36,7 +36,6 @@ limitations under the License. namespace tfq { using ::tensorflow::Status; -using ::tfq::proto::PauliSum; using ::tfq::proto::Program; typedef qsim::Cirq::GateCirq QsimGate; diff --git a/tensorflow_quantum/core/ops/parse_context.cc b/tensorflow_quantum/core/ops/parse_context.cc index 62404d421..4e5e61a6a 100644 --- a/tensorflow_quantum/core/ops/parse_context.cc +++ b/tensorflow_quantum/core/ops/parse_context.cc @@ -36,6 +36,7 @@ using ::tensorflow::OpKernelContext; using ::tensorflow::Status; using ::tensorflow::Tensor; using ::tfq::proto::PauliSum; +using ::tfq::proto::ProjectorSum; using ::tfq::proto::Program; template @@ -150,9 +151,11 @@ Status GetProgramsAndProgramsToAppend( Status GetProgramsAndNumQubits( OpKernelContext* context, std::vector* programs, std::vector* num_qubits, - std::vector>* p_sums /*=nullptr*/) { + std::vector>* p_sums /*=nullptr*/, + std::vector>* proj_sums /*=nullptr*/) { // 1. Parse input programs // 2. (Optional) Parse input PauliSums + // 2. (Optional) Parse input ProjectorSums. // 3. Convert GridQubit locations to integers. Status status = ParsePrograms(context, "programs", programs); if (!status.ok()) { @@ -173,6 +176,20 @@ Status GetProgramsAndNumQubits( } } + if (proj_sums) { + status = GetProjectorSums(context, proj_sums); + if (!status.ok()) { + return status; + } + if (programs->size() != proj_sums->size()) { + return Status( + tensorflow::error::INVALID_ARGUMENT, + absl::StrCat("Number of circuits and ProjectorSum do not match. Got ", + programs->size(), " circuits and ", proj_sums->size(), + " projectorsums.")); + } + } + // Resolve qubit ID's in parallel. num_qubits->assign(programs->size(), -1); auto DoWork = [&](int start, int end) { @@ -278,6 +295,43 @@ Status GetPauliSums(OpKernelContext* context, return Status::OK(); } +Status GetProjectorSums(OpKernelContext* context, + std::vector>* proj_sums) { + // 1. Parses ProjectorSum proto. + const Tensor* input; + Status status = context->input("projector_sums", &input); + if (!status.ok()) { + return status; + } + + if (input->dims() != 2) { + return Status(tensorflow::error::INVALID_ARGUMENT, + absl::StrCat("projector_sums must be rank 2. Got rank ", + input->dims(), ".")); + } + + const auto sum_specs = input->matrix(); + proj_sums->assign(sum_specs.dimension(0), + std::vector(sum_specs.dimension(1), ProjectorSum())); + const int op_dim = sum_specs.dimension(1); + auto DoWork = [&](int start, int end) { + for (int ii = start; ii < end; ii++) { + const int i = ii / op_dim; + const int j = ii % op_dim; + ProjectorSum p; + OP_REQUIRES_OK(context, ParseProto(sum_specs(i, j), &p)); + (*proj_sums)[i][j] = p; + } + }; + + // TODO(mbbrough): Determine if this is a good cycle estimate. + const int cycle_estimate = 1000; + context->device()->tensorflow_cpu_worker_threads()->workers->ParallelFor( + sum_specs.dimension(0) * sum_specs.dimension(1), cycle_estimate, DoWork); + + return Status::OK(); +} + Status GetSymbolMaps(OpKernelContext* context, std::vector* maps) { // 1. Convert to dictionary representation for param resolution. const Tensor* input_names; diff --git a/tensorflow_quantum/core/ops/parse_context.h b/tensorflow_quantum/core/ops/parse_context.h index 4cc214385..6b44b8b4b 100644 --- a/tensorflow_quantum/core/ops/parse_context.h +++ b/tensorflow_quantum/core/ops/parse_context.h @@ -32,6 +32,7 @@ limitations under the License. #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow_quantum/core/proto/pauli_sum.pb.h" +#include "tensorflow_quantum/core/proto/projector_sum.pb.h" #include "tensorflow_quantum/core/proto/program.pb.h" namespace tfq { @@ -64,7 +65,8 @@ typedef absl::flat_hash_map> SymbolMap; tensorflow::Status GetProgramsAndNumQubits( tensorflow::OpKernelContext* context, std::vector* programs, std::vector* num_qubits, - std::vector>* p_sums = nullptr); + std::vector>* p_sums = nullptr, + std::vector>* proj_sums = nullptr); // Parses Cirq Program protos out of the 'circuit_specs' input Tensor. Also // resolves the QubitIds inside of the Program. This override also parses and @@ -82,6 +84,13 @@ tensorflow::Status GetPauliSums( tensorflow::OpKernelContext* context, std::vector>* p_sums); +// Parses PauliSum protos out of the 'projector_sums' input tensor. Note this +// function does NOT resolve QubitID's as any projectorsum needs a reference +// program to "discover" all of the active qubits and define the ordering. +tensorflow::Status GetProjectorSums( + tensorflow::OpKernelContext* context, + std::vector>* proj_sums); + // Parses the input context to construct the SymbolMaps for the entire batch. // The two input Tensors are expected to be of size: // diff --git a/tensorflow_quantum/core/serialize/op_deserializer_test.py b/tensorflow_quantum/core/serialize/op_deserializer_test.py index f97b2db18..c5e56fb3d 100644 --- a/tensorflow_quantum/core/serialize/op_deserializer_test.py +++ b/tensorflow_quantum/core/serialize/op_deserializer_test.py @@ -325,7 +325,6 @@ def test_from_proto_not_required_ok(self, q): result = deserializer.from_proto(serialized) self.assertEqual(result, GateWithAttribute(0.125)(q)) - def test_from_proto_missing_required_arg(self): """Error raised when required field is missing.""" deserializer = op_deserializer.GateOpDeserializer( diff --git a/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation_test.py b/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation_test.py index 60714b2e4..c3c07c74c 100644 --- a/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation_test.py +++ b/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation_test.py @@ -68,7 +68,6 @@ def test_sampled_expectation_symbol_input(self): sampled_expectation.SampledExpectation( differentiator=linear_combination.ForwardDifference()) - def test_sampled_expectation_instantiate_error(self): """Test that SampledExpectation errors with bad inputs.""" diff --git a/tensorflow_quantum/python/util.py b/tensorflow_quantum/python/util.py index 92ebeabee..29c798ef7 100644 --- a/tensorflow_quantum/python/util.py +++ b/tensorflow_quantum/python/util.py @@ -25,6 +25,7 @@ from tensorflow_quantum.core.proto import pauli_sum_pb2 from tensorflow_quantum.core.proto import program_pb2 +from tensorflow_quantum.core.proto import projector_sum_pb2 from tensorflow_quantum.core.serialize import serializer # Can't use set() since channels don't give proper support. @@ -259,6 +260,23 @@ def random_pauli_sums(qubits, max_sum_length, n_sums): return sums +def random_projector_sums(qubits, max_sum_length, n_sums): + """Generate a list of random cirq projector sums of length |n_sums|.""" + sums = [] + for _ in range(n_sums): + this_sum_length = np.random.randint(1, max_sum_length + 1) + terms = [] + for _ in range(this_sum_length): + term_length = np.random.randint(1, len(qubits) + 1) + this_term_qubits = random.sample(qubits, term_length) + this_term_ids = np.random.randint(0, 2, term_length) + terms.append( + cirq.ProjectorString(dict(zip(this_term_qubits, + this_term_ids)))) + sums.append(cirq.ProjectorSum.from_projector_strings(terms)) + return sums + + # There are no native convertible ops inside of this function. @tf.autograph.experimental.do_not_convert def convert_to_tensor(items_to_convert, deterministic_proto_serialize=False): @@ -317,6 +335,12 @@ def recur(items_to_convert, curr_type=None): tensored_items.append( serializer.serialize_paulisum(item).SerializeToString( deterministic=deterministic_proto_serialize)) + elif isinstance(item, (cirq.ProjectorSum, cirq.ProjectorString)) and\ + not curr_type == cirq.Circuit: + curr_type = cirq.ProjectorSum + tensored_items.append( + serializer.serialize_projectorsum(item).SerializeToString( + deterministic=deterministic_proto_serialize)) elif isinstance(item, cirq.Circuit) and\ not curr_type == cirq.PauliSum: curr_type = cirq.Circuit @@ -347,6 +371,14 @@ def _parse_single(item): obj.ParseFromString(item) out = serializer.deserialize_paulisum(obj) return out + except Exception: + pass + try: + # Return a ProjectorSum parsing. + obj = projector_sum_pb2.ProjectorSum() + obj.ParseFromString(item) + out = serializer.deserialize_projectorsum(obj) + return out except Exception: raise TypeError('Error decoding item: ' + str(item)) diff --git a/tensorflow_quantum/python/util_test.py b/tensorflow_quantum/python/util_test.py index f4cb3e085..0e55d80bf 100644 --- a/tensorflow_quantum/python/util_test.py +++ b/tensorflow_quantum/python/util_test.py @@ -24,12 +24,16 @@ def _single_to_tensor(item): - if not isinstance(item, (cirq.PauliSum, cirq.PauliString, cirq.Circuit)): + if not isinstance(item, (cirq.PauliSum, cirq.PauliString, cirq.ProjectorSum, + cirq.ProjectorString, cirq.Circuit)): 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( deterministic=True) + if isinstance(item, (cirq.ProjectorSum, cirq.ProjectorString)): + return serializer.serialize_projectorsum(item).SerializeToString( + deterministic=True) return serializer.serialize_circuit(item).SerializeToString( deterministic=True) @@ -46,15 +50,18 @@ def _items_to_tensorize(): """Objects on which convert_to_tensor convert_from_tensor will be tested.""" return [{ 'item': x - } for x in (util.random_pauli_sums(BITS, 5, 5) + [ - cirq.PauliSum.from_pauli_strings([ - cirq.PauliString(), - cirq.PauliString(cirq.Z(cirq.GridQubit(0, 0))) - ]) - ] + [cirq.PauliString(), cirq.PauliString()] + [cirq.Circuit()] + [ - cirq.testing.random_circuit(BITS, 25, 0.9, util.get_supported_gates()) - for _ in range(5) - ])] + } for x in (util.random_pauli_sums(BITS, 5, 5) + + util.random_projector_sums(BITS, 5, 5) + [ + cirq.PauliSum.from_pauli_strings([ + cirq.PauliString(), + cirq.PauliString(cirq.Z(cirq.GridQubit(0, 0))) + ]) + ] + [cirq.PauliString(), cirq.PauliString()] + + [cirq.Circuit()] + [ + cirq.testing.random_circuit(BITS, 25, 0.9, + util.get_supported_gates()) + for _ in range(5) + ])] class UtilFunctionsTest(tf.test.TestCase, parameterized.TestCase): From 7a165a95f092320cea312c179c4fe2a89050374f Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Fri, 19 Nov 2021 05:30:24 +0000 Subject: [PATCH 02/20] nits --- tensorflow_quantum/python/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow_quantum/python/util.py b/tensorflow_quantum/python/util.py index 29c798ef7..2d58254b5 100644 --- a/tensorflow_quantum/python/util.py +++ b/tensorflow_quantum/python/util.py @@ -335,8 +335,8 @@ def recur(items_to_convert, curr_type=None): tensored_items.append( serializer.serialize_paulisum(item).SerializeToString( deterministic=deterministic_proto_serialize)) - elif isinstance(item, (cirq.ProjectorSum, cirq.ProjectorString)) and\ - not curr_type == cirq.Circuit: + elif isinstance(item, (cirq.ProjectorSum, cirq.ProjectorString)) \ + and not curr_type == cirq.Circuit: curr_type = cirq.ProjectorSum tensored_items.append( serializer.serialize_projectorsum(item).SerializeToString( From 2a04237c20b29d1120bfcea1bbab936e88a05cb1 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Fri, 19 Nov 2021 05:41:50 +0000 Subject: [PATCH 03/20] nits --- tensorflow_quantum/core/ops/parse_context.cc | 9 +++++---- tensorflow_quantum/core/ops/parse_context.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tensorflow_quantum/core/ops/parse_context.cc b/tensorflow_quantum/core/ops/parse_context.cc index 4e5e61a6a..15a4e0edf 100644 --- a/tensorflow_quantum/core/ops/parse_context.cc +++ b/tensorflow_quantum/core/ops/parse_context.cc @@ -36,8 +36,8 @@ using ::tensorflow::OpKernelContext; using ::tensorflow::Status; using ::tensorflow::Tensor; using ::tfq::proto::PauliSum; -using ::tfq::proto::ProjectorSum; using ::tfq::proto::Program; +using ::tfq::proto::ProjectorSum; template Status ParseProto(const std::string& text, T* proto) { @@ -296,7 +296,7 @@ Status GetPauliSums(OpKernelContext* context, } Status GetProjectorSums(OpKernelContext* context, - std::vector>* proj_sums) { + std::vector>* proj_sums) { // 1. Parses ProjectorSum proto. const Tensor* input; Status status = context->input("projector_sums", &input); @@ -311,8 +311,9 @@ Status GetProjectorSums(OpKernelContext* context, } const auto sum_specs = input->matrix(); - proj_sums->assign(sum_specs.dimension(0), - std::vector(sum_specs.dimension(1), ProjectorSum())); + proj_sums->assign( + sum_specs.dimension(0), + std::vector(sum_specs.dimension(1), ProjectorSum())); const int op_dim = sum_specs.dimension(1); auto DoWork = [&](int start, int end) { for (int ii = start; ii < end; ii++) { diff --git a/tensorflow_quantum/core/ops/parse_context.h b/tensorflow_quantum/core/ops/parse_context.h index 6b44b8b4b..e59667e57 100644 --- a/tensorflow_quantum/core/ops/parse_context.h +++ b/tensorflow_quantum/core/ops/parse_context.h @@ -32,8 +32,8 @@ limitations under the License. #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow_quantum/core/proto/pauli_sum.pb.h" -#include "tensorflow_quantum/core/proto/projector_sum.pb.h" #include "tensorflow_quantum/core/proto/program.pb.h" +#include "tensorflow_quantum/core/proto/projector_sum.pb.h" namespace tfq { From eb373ed029e594e9dc3ed1b95002effcbd616b76 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Sun, 21 Nov 2021 20:22:24 +0000 Subject: [PATCH 04/20] Intermediate --- .../core/ops/noise/noisy_expectation_op.py | 8 +- .../ops/noise/noisy_expectation_op_test.py | 133 +++++++++++++++--- .../core/ops/noise/tfq_noisy_expectation.cc | 29 +++- tensorflow_quantum/core/ops/parse_context.cc | 11 +- 4 files changed, 144 insertions(+), 37 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py index e621988a0..bae6d4806 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py @@ -20,7 +20,8 @@ NOISY_OP_MODULE = load_module(os.path.join("noise", "_tfq_noise_ops.so")) -def expectation(programs, symbol_names, symbol_values, pauli_sums, num_samples): +def expectation(programs, symbol_names, symbol_values, pauli_sums, + projector_sums, num_samples): """Calculate the analytic expectation values using monte-carlo trajectories. Simulate the final state of `programs` given `symbol_values` are placed @@ -80,6 +81,9 @@ def expectation(programs, symbol_names, symbol_values, pauli_sums, num_samples): pauli_sums: `tf.Tensor` of strings with shape [batch_size, n_ops] containing the string representation of the operators that will be used on all of the circuits in the expectation calculations. + projector_sums: `tf.Tensor` of strings with shape [batch_size, n_ops] + containing the string representation of the operators that will + be used on all of the circuits in the expectation calculations. num_samples: `tf.Tensor` with `num_samples[i][j]` is equal to the number of times `programs[i]` will be simulated to estimate `pauli_sums[i][j]`. Therefore, `num_samples` must have the same @@ -95,4 +99,4 @@ def expectation(programs, symbol_names, symbol_values, pauli_sums, num_samples): """ return NOISY_OP_MODULE.tfq_noisy_expectation( programs, symbol_names, tf.cast(symbol_values, tf.float32), pauli_sums, - tf.cast(num_samples, dtype=tf.int32)) + projector_sums, tf.cast(num_samples, dtype=tf.int32)) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py index 0196f504e..43295a4b4 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py @@ -42,6 +42,7 @@ def test_noisy_expectation_inputs(self): for resolver in resolver_batch]) pauli_sums = util.random_pauli_sums(qubits, 3, batch_size) + projector_sums = util.random_projector_sums(qubits, 3, batch_size) num_samples = [[10]] * batch_size with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -50,7 +51,9 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor([circuit_batch]), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_names must be rank 1.'): @@ -58,7 +61,9 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), np.array([symbol_names]), symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_values must be rank 2.'): @@ -66,7 +71,9 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, np.array([symbol_values_array]), - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_values must be rank 2.'): @@ -74,15 +81,27 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array[0], - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'pauli_sums must be rank 2.'): # pauli_sums tensor has too few dimensions. noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), - symbol_names, symbol_values_array, - util.convert_to_tensor(list(pauli_sums)), num_samples) + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, util.convert_to_tensor(list(pauli_sums)), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'projector_sums must be rank 2.'): + # pauli_sums tensor has too few dimensions. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor(list(projector_sums)), num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'pauli_sums must be rank 2.'): @@ -91,6 +110,17 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, [util.convert_to_tensor([[x] for x in pauli_sums])], + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'projector_sums must be rank 2.'): + # pauli_sums tensor has too many dimensions. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + [util.convert_to_tensor([[x] for x in projector_sums])], num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -100,6 +130,7 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), [num_samples]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -109,6 +140,7 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), num_samples[0]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -116,7 +148,9 @@ def test_noisy_expectation_inputs(self): # circuit tensor has the right type but invalid values. noisy_expectation_op.expectation( ['junk'] * batch_size, symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Could not find symbol in parameter map'): @@ -124,7 +158,9 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), ['junk'], symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'qubits not found in circuit'): @@ -135,40 +171,70 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in new_pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), num_samples) + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, # DO NOT SUBMIT re-enable + # 'qubits not found in circuit'): + # # pauli_sums tensor has the right type but invalid values. + # new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] + # new_projector_sums = util.random_pauli_sums(new_qubits, 2, batch_size) + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in new_projector_sums]), + # num_samples) + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Unparseable proto'): # pauli_sums tensor has the right type but invalid values 2. noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [['junk']] * batch_size, num_samples) + symbol_values_array, [['junk']] * batch_size, + [['junk']] * batch_size, num_samples) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # circuits tensor has the wrong type. noisy_expectation_op.expectation( [1.0] * batch_size, symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # symbol_names tensor has the wrong type. noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), [0.1234], symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.UnimplementedError, ''): # symbol_values tensor has the wrong type. noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, [['junk']] * batch_size, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # pauli_sums tensor has the wrong type. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, [[1.0]] * batch_size, + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # pauli_sums tensor has the wrong type. noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [[1.0]] * batch_size, num_samples) + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + [[1.0]] * batch_size, num_samples) with self.assertRaisesRegex(TypeError, 'missing'): # we are missing an argument. @@ -183,7 +249,8 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), [], + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), [], num_samples) # pylint: enable=too-many-function-args @@ -193,7 +260,9 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor([cirq.Circuit()]), symbol_names, symbol_values_array.astype(np.float64), - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'greater than 0'): @@ -202,6 +271,7 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), [[-1]] * batch_size) # pylint: enable=too-many-function-args @@ -211,7 +281,9 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array[:int(batch_size * 0.5)], - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) @parameterized.parameters([ { @@ -252,12 +324,17 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): pauli_sums1 = util.random_pauli_sums(qubits, 3, batch_size) pauli_sums2 = util.random_pauli_sums(qubits, 3, batch_size) batch_pauli_sums = [[x, y] for x, y in zip(pauli_sums1, pauli_sums2)] + projector_sums1 = util.random_projector_sums(qubits, 3, batch_size) + projector_sums2 = util.random_projector_sums(qubits, 3, batch_size) + batch_projector_sums = [ + [x, y] for x, y in zip(projector_sums1, projector_sums2) + ] num_samples = [[10000 if noisy else 3] * 2] * batch_size op_exps = noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), - symbol_names, symbol_values_array, - util.convert_to_tensor(batch_pauli_sums), num_samples) + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, util.convert_to_tensor(batch_pauli_sums), + util.convert_to_tensor(batch_projector_sums), num_samples) cirq_exps = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, batch_pauli_sums, @@ -290,12 +367,17 @@ def test_single_channel(self, channel): pauli_sums1 = util.random_pauli_sums(qubits, 3, batch_size) pauli_sums2 = util.random_pauli_sums(qubits, 3, batch_size) batch_pauli_sums = [[x, y] for x, y in zip(pauli_sums1, pauli_sums2)] + projector_sums1 = util.random_projector_sums(qubits, 3, batch_size) + projector_sums2 = util.random_projector_sums(qubits, 3, batch_size) + batch_projector_sums = [ + [x, y] for x, y in zip(projector_sums1, projector_sums2) + ] num_samples = [[10000] * 2] * batch_size op_exps = noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), - symbol_names, symbol_values_array, - util.convert_to_tensor(batch_pauli_sums), num_samples) + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, util.convert_to_tensor(batch_pauli_sums), + util.convert_to_tensor(batch_projector_sums), num_samples) cirq_exps = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, batch_pauli_sums, @@ -309,10 +391,13 @@ def test_correctness_empty(self): empty_symbols = tf.convert_to_tensor([], dtype=tf.dtypes.string) empty_values = tf.convert_to_tensor([[]]) empty_paulis = tf.convert_to_tensor([[]], dtype=tf.dtypes.string) + empty_projector_sums = tf.convert_to_tensor([[]], + dtype=tf.dtypes.string) empty_n_samples = tf.convert_to_tensor([[]], dtype=tf.int32) out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, empty_values, empty_paulis, + empty_projector_sums, empty_n_samples) expected = np.array([[]], dtype=np.complex64) @@ -324,10 +409,12 @@ def test_correctness_no_circuit(self): empty_symbols = tf.raw_ops.Empty(shape=(0,), dtype=tf.string) empty_values = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.float32) empty_paulis = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) + empty_projector_sums = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) empty_n_samples = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.int32) out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, empty_values, empty_paulis, + empty_projector_sums, empty_n_samples) self.assertShapeEqual(np.zeros((0, 0)), out) diff --git a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc index 88b78166e..4a2c0d132 100644 --- a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc +++ b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc @@ -48,6 +48,7 @@ namespace tfq { using ::tensorflow::Status; using ::tfq::proto::PauliSum; using ::tfq::proto::Program; +using ::tfq::proto::ProjectorSum; typedef qsim::Cirq::GateCirq QsimGate; typedef qsim::Circuit QsimCircuit; @@ -61,9 +62,9 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { void Compute(tensorflow::OpKernelContext* context) override { // TODO (mbbrough): add more dimension checks for other inputs here. const int num_inputs = context->num_inputs(); - OP_REQUIRES(context, num_inputs == 5, + OP_REQUIRES(context, num_inputs == 6, tensorflow::errors::InvalidArgument(absl::StrCat( - "Expected 5 inputs, got ", num_inputs, " inputs."))); + "Expected 6 inputs, got ", num_inputs, " inputs."))); // Create the output Tensor. const int output_dim_batch_size = context->input(0).dim_size(0); @@ -79,8 +80,10 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { std::vector programs; std::vector num_qubits; std::vector> pauli_sums; - OP_REQUIRES_OK(context, GetProgramsAndNumQubits(context, &programs, - &num_qubits, &pauli_sums)); + std::vector> projector_sums; + OP_REQUIRES_OK(context, + GetProgramsAndNumQubits(context, &programs, &num_qubits, + &pauli_sums, &projector_sums)); std::vector maps; OP_REQUIRES_OK(context, GetSymbolMaps(context, &maps)); @@ -101,12 +104,20 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { pauli_sums.size(), " lists of pauli sums."))); OP_REQUIRES( - context, context->input(4).dim_size(1) == context->input(3).dim_size(1), + context, context->input(5).dim_size(1) == context->input(3).dim_size(1), tensorflow::errors::InvalidArgument(absl::StrCat( "Dimension 1 of num_samples and pauli_sums do not match.", "Got ", - context->input(4).dim_size(1), " lists of sample sizes and ", + context->input(5).dim_size(1), " lists of sample sizes and ", context->input(3).dim_size(1), " lists of pauli sums."))); + OP_REQUIRES( + context, context->input(5).dim_size(1) == context->input(4).dim_size(1), + tensorflow::errors::InvalidArgument(absl::StrCat( + "Dimension 1 of num_samples and projector_sums do not match.", + "Got ", context->input(5).dim_size(1), + " lists of sample sizes and ", context->input(4).dim_size(1), + " lists of projector sums."))); + // Construct qsim circuits. std::vector qsim_circuits(programs.size(), NoisyQsimCircuit()); @@ -394,6 +405,7 @@ REGISTER_OP("TfqNoisyExpectation") .Input("symbol_names: string") .Input("symbol_values: float") .Input("pauli_sums: string") + .Input("projector_sums: string") .Input("num_samples: int32") .Output("expectations: float") .SetShapeFn([](tensorflow::shape_inference::InferenceContext* c) { @@ -409,8 +421,11 @@ REGISTER_OP("TfqNoisyExpectation") tensorflow::shape_inference::ShapeHandle pauli_sums_shape; TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 2, &pauli_sums_shape)); + tensorflow::shape_inference::ShapeHandle projecctor_sums_shape; + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 2, &projecctor_sums_shape)); + tensorflow::shape_inference::ShapeHandle num_samples_shape; - TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 2, &num_samples_shape)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 2, &num_samples_shape)); tensorflow::shape_inference::DimensionHandle output_rows = c->Dim(programs_shape, 0); diff --git a/tensorflow_quantum/core/ops/parse_context.cc b/tensorflow_quantum/core/ops/parse_context.cc index 15a4e0edf..1e74793b6 100644 --- a/tensorflow_quantum/core/ops/parse_context.cc +++ b/tensorflow_quantum/core/ops/parse_context.cc @@ -155,8 +155,8 @@ Status GetProgramsAndNumQubits( std::vector>* proj_sums /*=nullptr*/) { // 1. Parse input programs // 2. (Optional) Parse input PauliSums - // 2. (Optional) Parse input ProjectorSums. - // 3. Convert GridQubit locations to integers. + // 3. (Optional) Parse input ProjectorSums + // 4. Convert GridQubit locations to integers. Status status = ParsePrograms(context, "programs", programs); if (!status.ok()) { return status; @@ -184,9 +184,10 @@ Status GetProgramsAndNumQubits( if (programs->size() != proj_sums->size()) { return Status( tensorflow::error::INVALID_ARGUMENT, - absl::StrCat("Number of circuits and ProjectorSum do not match. Got ", - programs->size(), " circuits and ", proj_sums->size(), - " projectorsums.")); + absl::StrCat( + "Number of circuits and ProjectorSums do not match. Got ", + programs->size(), " circuits and ", proj_sums->size(), + " projectorsums.")); } } From a92bff7dadad67f0f8b301cefe850099fd965e60 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Sun, 21 Nov 2021 21:08:41 +0000 Subject: [PATCH 05/20] intermediate --- .../ops/noise/noisy_expectation_op_test.py | 23 ++++++++++--------- tensorflow_quantum/core/ops/parse_context.cc | 6 +++-- .../core/src/program_resolution.cc | 23 ++++++++++++++++++- .../core/src/program_resolution.h | 4 +++- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py index 43295a4b4..acedc25be 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py @@ -174,17 +174,18 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor([[x] for x in projector_sums]), num_samples) - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, # DO NOT SUBMIT re-enable - # 'qubits not found in circuit'): - # # pauli_sums tensor has the right type but invalid values. - # new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] - # new_projector_sums = util.random_pauli_sums(new_qubits, 2, batch_size) - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in new_projector_sums]), - # num_samples) + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'qubits not found in circuit'): + # pauli_sums tensor has the right type but invalid values. + new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] + new_projector_sums = util.random_pauli_sums(new_qubits, 2, + batch_size) + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in new_projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Unparseable proto'): diff --git a/tensorflow_quantum/core/ops/parse_context.cc b/tensorflow_quantum/core/ops/parse_context.cc index 1e74793b6..d33321435 100644 --- a/tensorflow_quantum/core/ops/parse_context.cc +++ b/tensorflow_quantum/core/ops/parse_context.cc @@ -197,9 +197,11 @@ Status GetProgramsAndNumQubits( for (int i = start; i < end; i++) { Program& program = (*programs)[i]; unsigned int this_num_qubits; - if (p_sums) { + if (p_sums || proj_sums) { + auto iter_p_sums = p_sums ? &(p_sums->at(i)) : nullptr; + auto iter_proj_sums = proj_sums ? &(proj_sums->at(i)) : nullptr; OP_REQUIRES_OK(context, ResolveQubitIds(&program, &this_num_qubits, - &(p_sums->at(i)))); + iter_p_sums, iter_proj_sums)); } else { OP_REQUIRES_OK(context, ResolveQubitIds(&program, &this_num_qubits)); } diff --git a/tensorflow_quantum/core/src/program_resolution.cc b/tensorflow_quantum/core/src/program_resolution.cc index 4a8c39ea0..95c8fb376 100644 --- a/tensorflow_quantum/core/src/program_resolution.cc +++ b/tensorflow_quantum/core/src/program_resolution.cc @@ -38,6 +38,9 @@ using tfq::proto::PauliQubitPair; using tfq::proto::PauliSum; using tfq::proto::PauliTerm; using tfq::proto::Program; +using tfq::proto::ProjectorDictEntry; +using tfq::proto::ProjectorSum; +using tfq::proto::ProjectorTerm; using tfq::proto::Qubit; inline absl::string_view IntMaxStr() { @@ -83,7 +86,8 @@ Status RegisterQubits( } Status ResolveQubitIds(Program* program, unsigned int* num_qubits, - std::vector* p_sums /*=nullptr*/) { + std::vector* p_sums /*=nullptr*/, + std::vector* projector_sums /*=nullptr*/) { if (program->circuit().moments().empty()) { // (#679) Just ignore empty program. // Number of qubits in empty programs is zero. @@ -166,6 +170,23 @@ Status ResolveQubitIds(Program* program, unsigned int* num_qubits, } } + if (projector_sums) { + for (size_t i = 0; i < projector_sums->size(); i++) { + // Replace the ProjectorSum Qubit ids with the indices. + for (ProjectorTerm& term : *(projector_sums->at(i)).mutable_terms()) { + for (ProjectorDictEntry& pair : *term.mutable_projector_dict()) { + const auto result = id_to_index.find(pair.qubit_id()); + if (result == id_to_index.end()) { + return Status(tensorflow::error::INVALID_ARGUMENT, + "Found a Projector sum operating on qubits not found " + "in circuit."); + } + pair.set_qubit_id(result->second); + } + } + } + } + return Status::OK(); } diff --git a/tensorflow_quantum/core/src/program_resolution.h b/tensorflow_quantum/core/src/program_resolution.h index 6889401b2..aa85b3887 100644 --- a/tensorflow_quantum/core/src/program_resolution.h +++ b/tensorflow_quantum/core/src/program_resolution.h @@ -25,6 +25,7 @@ limitations under the License. #include "tensorflow/core/lib/core/status.h" #include "tensorflow_quantum/core/proto/pauli_sum.pb.h" #include "tensorflow_quantum/core/proto/program.pb.h" +#include "tensorflow_quantum/core/proto/projector_sum.pb.h" namespace tfq { @@ -37,7 +38,8 @@ namespace tfq { // The number of qubits in the program is recorded in `num_qubits`. tensorflow::Status ResolveQubitIds( tfq::proto::Program* program, unsigned int* num_qubits, - std::vector* p_sums = nullptr); + std::vector* p_sums = nullptr, + std::vector* projector_sums = nullptr); // Overload which allows for strict resolution of multiple programs. // Will resolve GridQubits in `program` and then double check that From ba02b65f43b733a7488a5cb92f49a060ac64deaa Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Tue, 23 Nov 2021 05:35:21 +0000 Subject: [PATCH 06/20] intermediate --- .../ops/noise/noisy_expectation_op_test.py | 6 ++- .../core/ops/noise/tfq_noisy_expectation.cc | 42 +++++++++++++++++-- tensorflow_quantum/core/src/util_qsim.h | 10 +++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py index acedc25be..c759d7ce4 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py @@ -330,6 +330,7 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): batch_projector_sums = [ [x, y] for x, y in zip(projector_sums1, projector_sums2) ] + batch_both = [[x, y, z, t] for x, y, z, t in zip(pauli_sums1, pauli_sums2, projector_sums1, projector_sums2)] num_samples = [[10000 if noisy else 3] * 2] * batch_size op_exps = noisy_expectation_op.expectation( @@ -338,7 +339,7 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): util.convert_to_tensor(batch_projector_sums), num_samples) cirq_exps = batch_util.batch_calculate_expectation( - circuit_batch, resolver_batch, batch_pauli_sums, + circuit_batch, resolver_batch, batch_both, cirq.DensityMatrixSimulator() if noisy else cirq.Simulator()) tol = 5e-2 if noisy else 5e-4 self.assertAllClose(cirq_exps, op_exps, atol=tol, rtol=tol) @@ -373,6 +374,7 @@ def test_single_channel(self, channel): batch_projector_sums = [ [x, y] for x, y in zip(projector_sums1, projector_sums2) ] + batch_both = [[x, y, z, t] for x, y, z, t in zip(pauli_sums1, pauli_sums2, projector_sums1, projector_sums2)] num_samples = [[10000] * 2] * batch_size op_exps = noisy_expectation_op.expectation( @@ -381,7 +383,7 @@ def test_single_channel(self, channel): util.convert_to_tensor(batch_projector_sums), num_samples) cirq_exps = batch_util.batch_calculate_expectation( - circuit_batch, resolver_batch, batch_pauli_sums, + circuit_batch, resolver_batch, batch_both, cirq.DensityMatrixSimulator()) self.assertAllClose(cirq_exps, op_exps, atol=5e-2, rtol=5e-2) diff --git a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc index 4a2c0d132..d1cc40a86 100644 --- a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc +++ b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc @@ -103,6 +103,12 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { "Got ", num_samples.size(), " lists of sample sizes and ", pauli_sums.size(), " lists of pauli sums."))); + OP_REQUIRES(context, num_samples.size() == projector_sums.size(), + tensorflow::errors::InvalidArgument(absl::StrCat( + "Dimension 0 of num_samples and projector_sums do not match.", + "Got ", num_samples.size(), " lists of sample sizes and ", + projector_sums.size(), " lists of projector sums."))); + OP_REQUIRES( context, context->input(5).dim_size(1) == context->input(3).dim_size(1), tensorflow::errors::InvalidArgument(absl::StrCat( @@ -152,12 +158,12 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { // alternate parallelization scheme with runtime: // O(n_circuits * max_j(num_samples[i])) with parallelization being // multiple threads per wavefunction. - ComputeLarge(num_qubits, qsim_circuits, pauli_sums, num_samples, context, + ComputeLarge(num_qubits, qsim_circuits, pauli_sums, projector_sums, num_samples, context, &output_tensor); } else { // Runtime: O(n_circuits * max_j(num_samples[i])) with parallelization // being done over number of trials. - ComputeSmall(num_qubits, max_num_qubits, qsim_circuits, pauli_sums, + ComputeSmall(num_qubits, max_num_qubits, qsim_circuits, pauli_sums, projector_sums, num_samples, context, &output_tensor); } } @@ -166,6 +172,7 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { void ComputeLarge(const std::vector& num_qubits, const std::vector& ncircuits, const std::vector>& pauli_sums, + const std::vector>& projector_sums, const std::vector>& num_samples, tensorflow::OpKernelContext* context, tensorflow::TTypes::Matrix* output_tensor) { @@ -242,6 +249,17 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { rolling_sums[j] += static_cast(exp_v); run_samples[j]++; } + for (int j = 0; j < projector_sums[i].size(); j++) { + if (run_samples[j] >= num_samples[i][j]) { + continue; + } + float exp_v = 0.0; + OP_REQUIRES_OK(context, + ComputeExpectationQsim(projector_sums[i][j], sim, ss, sv, + scratch, &exp_v)); + rolling_sums[j] += static_cast(exp_v); + run_samples[j]++; + } bool break_loop = true; for (int j = 0; j < num_samples[i].size(); j++) { if (run_samples[j] < num_samples[i][j]) { @@ -264,6 +282,7 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { const int max_num_qubits, const std::vector& ncircuits, const std::vector>& pauli_sums, + const std::vector>& projector_sums, const std::vector>& num_samples, tensorflow::OpKernelContext* context, tensorflow::TTypes::Matrix* output_tensor) { @@ -363,6 +382,21 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { run_samples[j]++; } + for (int j = 0; j < projector_sums[i].size(); j++) { + int p_reps = (num_samples[i][j] + num_threads - 1) / num_threads; + if (run_samples[j] >= p_reps + rep_offset) { + continue; + } + float exp_v = 0.0; + NESTED_FN_STATUS_SYNC( + compute_status, + ComputeExpectationQsim(projector_sums[i][j], sim, ss, sv, scratch, + &exp_v), + c_lock); + rolling_sums[j] += static_cast(exp_v); + run_samples[j]++; + } + // Check if we have run enough trajectories for all ops. bool break_loop = true; for (int j = 0; j < num_samples[i].size(); j++) { @@ -421,8 +455,8 @@ REGISTER_OP("TfqNoisyExpectation") tensorflow::shape_inference::ShapeHandle pauli_sums_shape; TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 2, &pauli_sums_shape)); - tensorflow::shape_inference::ShapeHandle projecctor_sums_shape; - TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 2, &projecctor_sums_shape)); + tensorflow::shape_inference::ShapeHandle projector_sums_shape; + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 2, &projector_sums_shape)); tensorflow::shape_inference::ShapeHandle num_samples_shape; TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 2, &num_samples_shape)); diff --git a/tensorflow_quantum/core/src/util_qsim.h b/tensorflow_quantum/core/src/util_qsim.h index 933f1582d..551f4ce34 100644 --- a/tensorflow_quantum/core/src/util_qsim.h +++ b/tensorflow_quantum/core/src/util_qsim.h @@ -178,6 +178,16 @@ tensorflow::Status ComputeExpectationQsim(const tfq::proto::PauliSum& p_sum, return status; } +template +tensorflow::Status ComputeExpectationQsim(const tfq::proto::ProjectorSum& projector_sum, + const SimT& sim, + const StateSpaceT& ss, StateT& state, + StateT& scratch, + float* expectation_value) { + *expectation_value = 0.0; // DO NOT SUBMIT + return tensorflow::Status::OK(); // DO NOT SUBMIT +} + // bad style standards here that we are forced to follow from qsim. // computes the expectation value using // scratch to save on memory. Implementation does this: From 4b97fb6b75a06cfce1481590ec1dd1170a902805 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Wed, 24 Nov 2021 05:09:26 +0000 Subject: [PATCH 07/20] intermediate --- tensorflow_quantum/core/ops/batch_util.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tensorflow_quantum/core/ops/batch_util.py b/tensorflow_quantum/core/ops/batch_util.py index 75f148424..fad31c476 100644 --- a/tensorflow_quantum/core/ops/batch_util.py +++ b/tensorflow_quantum/core/ops/batch_util.py @@ -23,7 +23,7 @@ # be used end to end with engine. This current issue is that # cirq.PauliSumCollector does not produce serializable gates for basis # conversion. -class TFQPauliSumCollector(cirq.work.collector.Collector): +class TFQPauliProjectorSumCollector(cirq.work.collector.Collector): """Copy of cirq.PauliSumCollector with some fixes to work with engine.""" def __init__(self, @@ -261,7 +261,7 @@ def batch_calculate_expectation(circuits, param_resolvers, ops, simulator): if not isinstance(sub_list, (list, tuple, np.ndarray)): raise TypeError('elements of ops must be type list.') for x in sub_list: - if not isinstance(x, cirq.PauliSum): + if not isinstance(x, (cirq.PauliSum, cirq.ProjectorSum)): raise TypeError('ops must contain only cirq.PauliSum objects.' ' Given: {}'.format(type(x))) @@ -279,8 +279,13 @@ def batch_calculate_expectation(circuits, param_resolvers, ops, simulator): sim_result = simulator.simulate(c, p) for j, op in enumerate(op_row): dm = sim_result.final_density_matrix - all_exp_vals[i][j] = op.expectation_from_density_matrix( - dm, qubit_order, check_preconditions=False) + if isinstance(op, cirq.PauliSum): + all_exp_vals[i][j] = op.expectation_from_density_matrix( + dm, qubit_order, check_preconditions=False) + else: + assert isinstance(op, cirq.ProjectorSum) + all_exp_vals[i][j] = op.expectation_from_density_matrix( + dm, qubit_order) else: # Valid observables always have real expectation values. all_exp_vals[i] = np.real( @@ -359,7 +364,7 @@ def batch_calculate_sampled_expectation(circuits, param_resolvers, ops, continue circuit = cirq.resolve_parameters(c, params) for op_index, op in enumerate(ops[c_index]): - collector = TFQPauliSumCollector( + collector = TFQPauliProjectorSumCollector( circuit, op, samples_per_term=n_samples[c_index][op_index]) collector.collect(sampler) result = collector.estimated_energy().real From a41d28eab43895841bf5e3698ae33348087cc89c Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Fri, 26 Nov 2021 02:45:26 +0000 Subject: [PATCH 08/20] intermediate --- .../core/ops/noise/noisy_expectation_op.py | 1 + .../ops/noise/noisy_expectation_op_test.py | 710 +++++++++--------- .../core/ops/noise/tfq_noisy_expectation.cc | 52 +- tensorflow_quantum/core/ops/parse_context.cc | 2 +- .../core/src/circuit_parser_qsim.cc | 70 +- .../core/src/circuit_parser_qsim.h | 8 + tensorflow_quantum/core/src/util_qsim.h | 35 +- tensorflow_quantum/python/util.py | 2 +- 8 files changed, 501 insertions(+), 379 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py index bae6d4806..e7feb01d8 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py @@ -97,6 +97,7 @@ def expectation(programs, symbol_names, symbol_values, pauli_sums, expectation value for each circuit with each op applied to it (after resolving the corresponding parameters in). """ + print('TONYBOOM noisy_expectation_op()') return NOISY_OP_MODULE.tfq_noisy_expectation( programs, symbol_names, tf.cast(symbol_values, tf.float32), pauli_sums, projector_sums, tf.cast(num_samples, dtype=tf.int32)) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py index c759d7ce4..142d71a51 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py @@ -26,287 +26,287 @@ class NoisyExpectationCalculationTest(tf.test.TestCase, parameterized.TestCase): """Tests tfq.noise.expectation.""" - def test_noisy_expectation_inputs(self): - """Make sure noisy expectation op fails gracefully on bad inputs.""" - n_qubits = 5 - batch_size = 5 - symbol_names = ['alpha'] - qubits = cirq.GridQubit.rect(1, n_qubits) - circuit_batch, resolver_batch = \ - util.random_symbol_circuit_resolver_batch( - qubits, symbol_names, batch_size, include_channels=True) - - symbol_values_array = np.array( - [[resolver[symbol] - for symbol in symbol_names] - for resolver in resolver_batch]) - - pauli_sums = util.random_pauli_sums(qubits, 3, batch_size) - projector_sums = util.random_projector_sums(qubits, 3, batch_size) - num_samples = [[10]] * batch_size - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'programs must be rank 1'): - # Circuit tensor has too many dimensions. - noisy_expectation_op.expectation( - util.convert_to_tensor([circuit_batch]), symbol_names, - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'symbol_names must be rank 1.'): - # symbol_names tensor has too many dimensions. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), np.array([symbol_names]), - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'symbol_values must be rank 2.'): - # symbol_values_array tensor has too many dimensions. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - np.array([symbol_values_array]), - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'symbol_values must be rank 2.'): - # symbol_values_array tensor has too few dimensions. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array[0], - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'pauli_sums must be rank 2.'): - # pauli_sums tensor has too few dimensions. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, util.convert_to_tensor(list(pauli_sums)), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'projector_sums must be rank 2.'): - # pauli_sums tensor has too few dimensions. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor(list(projector_sums)), num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'pauli_sums must be rank 2.'): - # pauli_sums tensor has too many dimensions. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, - [util.convert_to_tensor([[x] for x in pauli_sums])], - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'projector_sums must be rank 2.'): - # pauli_sums tensor has too many dimensions. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - [util.convert_to_tensor([[x] for x in projector_sums])], - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'num_samples must be rank 2'): - # num_samples tensor has the wrong shape. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - [num_samples]) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'num_samples must be rank 2'): - # num_samples tensor has the wrong shape. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples[0]) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'Unparseable proto'): - # circuit tensor has the right type but invalid values. - noisy_expectation_op.expectation( - ['junk'] * batch_size, symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'Could not find symbol in parameter map'): - # symbol_names tensor has the right type but invalid values. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), ['junk'], - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'qubits not found in circuit'): - # pauli_sums tensor has the right type but invalid values. - new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] - new_pauli_sums = util.random_pauli_sums(new_qubits, 2, batch_size) - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, - util.convert_to_tensor([[x] for x in new_pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'qubits not found in circuit'): - # pauli_sums tensor has the right type but invalid values. - new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] - new_projector_sums = util.random_pauli_sums(new_qubits, 2, - batch_size) - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in new_projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'Unparseable proto'): - # pauli_sums tensor has the right type but invalid values 2. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [['junk']] * batch_size, - [['junk']] * batch_size, num_samples) - - with self.assertRaisesRegex(TypeError, 'Cannot convert'): - # circuits tensor has the wrong type. - noisy_expectation_op.expectation( - [1.0] * batch_size, symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(TypeError, 'Cannot convert'): - # symbol_names tensor has the wrong type. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), [0.1234], - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.UnimplementedError, ''): - # symbol_values tensor has the wrong type. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - [['junk']] * batch_size, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(TypeError, 'Cannot convert'): - # pauli_sums tensor has the wrong type. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [[1.0]] * batch_size, - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(TypeError, 'Cannot convert'): - # pauli_sums tensor has the wrong type. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - [[1.0]] * batch_size, num_samples) - - with self.assertRaisesRegex(TypeError, 'missing'): - # we are missing an argument. - # pylint: disable=no-value-for-parameter - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, num_samples) - # pylint: enable=no-value-for-parameter - - with self.assertRaisesRegex(TypeError, 'positional arguments'): - # pylint: disable=too-many-function-args - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), [], - num_samples) - # pylint: enable=too-many-function-args - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - expected_regex='do not match'): - # wrong op size. - noisy_expectation_op.expectation( - util.convert_to_tensor([cirq.Circuit()]), symbol_names, - symbol_values_array.astype(np.float64), - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - 'greater than 0'): - # pylint: disable=too-many-function-args - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - [[-1]] * batch_size) - # pylint: enable=too-many-function-args - - with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - expected_regex='do not match'): - # wrong symbol_values size. - noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array[:int(batch_size * 0.5)], - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + # def test_noisy_expectation_inputs(self): + # """Make sure noisy expectation op fails gracefully on bad inputs.""" + # n_qubits = 5 + # batch_size = 5 + # symbol_names = ['alpha'] + # qubits = cirq.GridQubit.rect(1, n_qubits) + # circuit_batch, resolver_batch = \ + # util.random_symbol_circuit_resolver_batch( + # qubits, symbol_names, batch_size, include_channels=True) + + # symbol_values_array = np.array( + # [[resolver[symbol] + # for symbol in symbol_names] + # for resolver in resolver_batch]) + + # pauli_sums = util.random_pauli_sums(qubits, 3, batch_size) + # projector_sums = util.random_projector_sums(qubits, 3, batch_size) + # num_samples = [[10]] * batch_size + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'programs must be rank 1'): + # # Circuit tensor has too many dimensions. + # noisy_expectation_op.expectation( + # util.convert_to_tensor([circuit_batch]), symbol_names, + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'symbol_names must be rank 1.'): + # # symbol_names tensor has too many dimensions. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), np.array([symbol_names]), + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'symbol_values must be rank 2.'): + # # symbol_values_array tensor has too many dimensions. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # np.array([symbol_values_array]), + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'symbol_values must be rank 2.'): + # # symbol_values_array tensor has too few dimensions. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array[0], + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'pauli_sums must be rank 2.'): + # # pauli_sums tensor has too few dimensions. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, util.convert_to_tensor(list(pauli_sums)), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'projector_sums must be rank 2.'): + # # pauli_sums tensor has too few dimensions. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor(list(projector_sums)), num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'pauli_sums must be rank 2.'): + # # pauli_sums tensor has too many dimensions. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, + # [util.convert_to_tensor([[x] for x in pauli_sums])], + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'projector_sums must be rank 2.'): + # # pauli_sums tensor has too many dimensions. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # [util.convert_to_tensor([[x] for x in projector_sums])], + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'num_samples must be rank 2'): + # # num_samples tensor has the wrong shape. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # [num_samples]) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'num_samples must be rank 2'): + # # num_samples tensor has the wrong shape. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples[0]) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'Unparseable proto'): + # # circuit tensor has the right type but invalid values. + # noisy_expectation_op.expectation( + # ['junk'] * batch_size, symbol_names, symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'Could not find symbol in parameter map'): + # # symbol_names tensor has the right type but invalid values. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), ['junk'], + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'qubits not found in circuit'): + # # pauli_sums tensor has the right type but invalid values. + # new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] + # new_pauli_sums = util.random_pauli_sums(new_qubits, 2, batch_size) + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, + # util.convert_to_tensor([[x] for x in new_pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'qubits not found in circuit'): + # # pauli_sums tensor has the right type but invalid values. + # new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] + # new_projector_sums = util.random_pauli_sums(new_qubits, 2, + # batch_size) + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in new_projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'Unparseable proto'): + # # pauli_sums tensor has the right type but invalid values 2. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, [['junk']] * batch_size, + # [['junk']] * batch_size, num_samples) + + # with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # # circuits tensor has the wrong type. + # noisy_expectation_op.expectation( + # [1.0] * batch_size, symbol_names, symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # # symbol_names tensor has the wrong type. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), [0.1234], + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.UnimplementedError, ''): + # # symbol_values tensor has the wrong type. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # [['junk']] * batch_size, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # # pauli_sums tensor has the wrong type. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, [[1.0]] * batch_size, + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # # pauli_sums tensor has the wrong type. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # [[1.0]] * batch_size, num_samples) + + # with self.assertRaisesRegex(TypeError, 'missing'): + # # we are missing an argument. + # # pylint: disable=no-value-for-parameter + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, num_samples) + # # pylint: enable=no-value-for-parameter + + # with self.assertRaisesRegex(TypeError, 'positional arguments'): + # # pylint: disable=too-many-function-args + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), [], + # num_samples) + # # pylint: enable=too-many-function-args + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # expected_regex='do not match'): + # # wrong op size. + # noisy_expectation_op.expectation( + # util.convert_to_tensor([cirq.Circuit()]), symbol_names, + # symbol_values_array.astype(np.float64), + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # 'greater than 0'): + # # pylint: disable=too-many-function-args + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # [[-1]] * batch_size) + # # pylint: enable=too-many-function-args + + # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + # expected_regex='do not match'): + # # wrong symbol_values size. + # noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array[:int(batch_size * 0.5)], + # util.convert_to_tensor([[x] for x in pauli_sums]), + # util.convert_to_tensor([[x] for x in projector_sums]), + # num_samples) @parameterized.parameters([ + # { + # 'n_qubits': 13, + # 'batch_size': 1, + # 'noisy': False + # }, # ComputeLarge. + # { + # 'n_qubits': 6, + # 'batch_size': 25, + # 'noisy': False + # }, # ComputeSmall. { - 'n_qubits': 13, - 'batch_size': 1, - 'noisy': False - }, # ComputeLarge. - { - 'n_qubits': 6, - 'batch_size': 25, - 'noisy': False - }, # ComputeSmall. - { - 'n_qubits': 6, - 'batch_size': 10, + 'n_qubits': 5, # DO NOT SUBMIT + 'batch_size': 7, # DO NOT SUBMIT 'noisy': True }, # ComputeSmall. - { - 'n_qubits': 8, - 'batch_size': 1, - 'noisy': True - } # ComputeLarge. + # { + # 'n_qubits': 8, + # 'batch_size': 1, + # 'noisy': True + # } # ComputeLarge. ]) def test_simulate_consistency(self, batch_size, n_qubits, noisy): """Test consistency with batch_util.py simulation.""" @@ -331,7 +331,7 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): [x, y] for x, y in zip(projector_sums1, projector_sums2) ] batch_both = [[x, y, z, t] for x, y, z, t in zip(pauli_sums1, pauli_sums2, projector_sums1, projector_sums2)] - num_samples = [[10000 if noisy else 3] * 2] * batch_size + num_samples = [[10000 if noisy else 3] * 4] * batch_size op_exps = noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, @@ -342,85 +342,87 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): circuit_batch, resolver_batch, batch_both, cirq.DensityMatrixSimulator() if noisy else cirq.Simulator()) tol = 5e-2 if noisy else 5e-4 + print(f'TONYBOOM op_exps.shape={op_exps.shape}') + print(f'TONYBOOM cirq_exps.shape={cirq_exps.shape}') self.assertAllClose(cirq_exps, op_exps, atol=tol, rtol=tol) - @parameterized.parameters([{ - 'channel': x - } for x in util.get_supported_channels()]) - def test_single_channel(self, channel): - """Individually test adding just a single channel type to circuits.""" - symbol_names = [] - batch_size = 5 - n_qubits = 6 - qubits = cirq.LineQubit.range(n_qubits) - - circuit_batch, resolver_batch = \ - util.random_circuit_resolver_batch( - qubits, batch_size, include_channels=False) - - for i in range(batch_size): - circuit_batch[i] = circuit_batch[i] + channel.on_each(*qubits) - - symbol_values_array = np.array( - [[resolver[symbol] - for symbol in symbol_names] - for resolver in resolver_batch]) - - pauli_sums1 = util.random_pauli_sums(qubits, 3, batch_size) - pauli_sums2 = util.random_pauli_sums(qubits, 3, batch_size) - batch_pauli_sums = [[x, y] for x, y in zip(pauli_sums1, pauli_sums2)] - projector_sums1 = util.random_projector_sums(qubits, 3, batch_size) - projector_sums2 = util.random_projector_sums(qubits, 3, batch_size) - batch_projector_sums = [ - [x, y] for x, y in zip(projector_sums1, projector_sums2) - ] - batch_both = [[x, y, z, t] for x, y, z, t in zip(pauli_sums1, pauli_sums2, projector_sums1, projector_sums2)] - num_samples = [[10000] * 2] * batch_size - - op_exps = noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, util.convert_to_tensor(batch_pauli_sums), - util.convert_to_tensor(batch_projector_sums), num_samples) - - cirq_exps = batch_util.batch_calculate_expectation( - circuit_batch, resolver_batch, batch_both, - cirq.DensityMatrixSimulator()) - - self.assertAllClose(cirq_exps, op_exps, atol=5e-2, rtol=5e-2) - - def test_correctness_empty(self): - """Test the expectation for empty circuits.""" - empty_circuit = util.convert_to_tensor([cirq.Circuit()]) - empty_symbols = tf.convert_to_tensor([], dtype=tf.dtypes.string) - empty_values = tf.convert_to_tensor([[]]) - empty_paulis = tf.convert_to_tensor([[]], dtype=tf.dtypes.string) - empty_projector_sums = tf.convert_to_tensor([[]], - dtype=tf.dtypes.string) - empty_n_samples = tf.convert_to_tensor([[]], dtype=tf.int32) - - out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, - empty_values, empty_paulis, - empty_projector_sums, - empty_n_samples) - - expected = np.array([[]], dtype=np.complex64) - self.assertAllClose(out, expected) - - def test_correctness_no_circuit(self): - """Test the correctness with the empty tensor.""" - empty_circuit = tf.raw_ops.Empty(shape=(0,), dtype=tf.string) - empty_symbols = tf.raw_ops.Empty(shape=(0,), dtype=tf.string) - empty_values = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.float32) - empty_paulis = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) - empty_projector_sums = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) - empty_n_samples = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.int32) - - out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, - empty_values, empty_paulis, - empty_projector_sums, - empty_n_samples) - - self.assertShapeEqual(np.zeros((0, 0)), out) + # @parameterized.parameters([{ + # 'channel': x + # } for x in util.get_supported_channels()]) + # def test_single_channel(self, channel): + # """Individually test adding just a single channel type to circuits.""" + # symbol_names = [] + # batch_size = 5 + # n_qubits = 6 + # qubits = cirq.LineQubit.range(n_qubits) + + # circuit_batch, resolver_batch = \ + # util.random_circuit_resolver_batch( + # qubits, batch_size, include_channels=False) + + # for i in range(batch_size): + # circuit_batch[i] = circuit_batch[i] + channel.on_each(*qubits) + + # symbol_values_array = np.array( + # [[resolver[symbol] + # for symbol in symbol_names] + # for resolver in resolver_batch]) + + # pauli_sums1 = util.random_pauli_sums(qubits, 3, batch_size) + # pauli_sums2 = util.random_pauli_sums(qubits, 3, batch_size) + # batch_pauli_sums = [[x, y] for x, y in zip(pauli_sums1, pauli_sums2)] + # projector_sums1 = util.random_projector_sums(qubits, 3, batch_size) + # projector_sums2 = util.random_projector_sums(qubits, 3, batch_size) + # batch_projector_sums = [ + # [x, y] for x, y in zip(projector_sums1, projector_sums2) + # ] + # batch_both = [[x, y, z, t] for x, y, z, t in zip(pauli_sums1, pauli_sums2, projector_sums1, projector_sums2)] + # num_samples = [[10000] * 2] * batch_size + + # op_exps = noisy_expectation_op.expectation( + # util.convert_to_tensor(circuit_batch), symbol_names, + # symbol_values_array, util.convert_to_tensor(batch_pauli_sums), + # util.convert_to_tensor(batch_projector_sums), num_samples) + + # cirq_exps = batch_util.batch_calculate_expectation( + # circuit_batch, resolver_batch, batch_both, + # cirq.DensityMatrixSimulator()) + + # self.assertAllClose(cirq_exps, op_exps, atol=5e-2, rtol=5e-2) + + # def test_correctness_empty(self): + # """Test the expectation for empty circuits.""" + # empty_circuit = util.convert_to_tensor([cirq.Circuit()]) + # empty_symbols = tf.convert_to_tensor([], dtype=tf.dtypes.string) + # empty_values = tf.convert_to_tensor([[]]) + # empty_paulis = tf.convert_to_tensor([[]], dtype=tf.dtypes.string) + # empty_projector_sums = tf.convert_to_tensor([[]], + # dtype=tf.dtypes.string) + # empty_n_samples = tf.convert_to_tensor([[]], dtype=tf.int32) + + # out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, + # empty_values, empty_paulis, + # empty_projector_sums, + # empty_n_samples) + + # expected = np.array([[]], dtype=np.complex64) + # self.assertAllClose(out, expected) + + # def test_correctness_no_circuit(self): + # """Test the correctness with the empty tensor.""" + # empty_circuit = tf.raw_ops.Empty(shape=(0,), dtype=tf.string) + # empty_symbols = tf.raw_ops.Empty(shape=(0,), dtype=tf.string) + # empty_values = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.float32) + # empty_paulis = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) + # empty_projector_sums = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) + # empty_n_samples = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.int32) + + # out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, + # empty_values, empty_paulis, + # empty_projector_sums, + # empty_n_samples) + + # self.assertShapeEqual(np.zeros((0, 0)), out) if __name__ == "__main__": diff --git a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc index d1cc40a86..9ebf288df 100644 --- a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc +++ b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc @@ -68,7 +68,9 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { // Create the output Tensor. const int output_dim_batch_size = context->input(0).dim_size(0); - const int output_dim_op_size = context->input(3).dim_size(1); + const int output_dim_op_size = context->input(3).dim_size(1) + context->input(4).dim_size(1); + LOG(INFO) << "TONYBOOM output_dim_batch_size=" << output_dim_batch_size; + LOG(INFO) << "TONYBOOM output_dim_op_size=" << output_dim_op_size; tensorflow::TensorShape output_shape; output_shape.AddDim(output_dim_batch_size); output_shape.AddDim(output_dim_op_size); @@ -110,19 +112,12 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { projector_sums.size(), " lists of projector sums."))); OP_REQUIRES( - context, context->input(5).dim_size(1) == context->input(3).dim_size(1), + context, context->input(5).dim_size(1) == context->input(3).dim_size(1) + context->input(4).dim_size(1), tensorflow::errors::InvalidArgument(absl::StrCat( - "Dimension 1 of num_samples and pauli_sums do not match.", "Got ", + "Dimension 1 of num_samples and pauli_sums + projector_sums do not match.", "Got ", context->input(5).dim_size(1), " lists of sample sizes and ", - context->input(3).dim_size(1), " lists of pauli sums."))); - - OP_REQUIRES( - context, context->input(5).dim_size(1) == context->input(4).dim_size(1), - tensorflow::errors::InvalidArgument(absl::StrCat( - "Dimension 1 of num_samples and projector_sums do not match.", - "Got ", context->input(5).dim_size(1), - " lists of sample sizes and ", context->input(4).dim_size(1), - " lists of projector sums."))); + context->input(3).dim_size(1), " lists of pauli sums and ", + context->input(4).dim_size(1), " lists of projector sums."))); // Construct qsim circuits. std::vector qsim_circuits(programs.size(), @@ -153,6 +148,21 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { // e2s2 = 2 CPU, 8GB -> Can safely do 25 since Memory = 4GB // e2s4 = 4 CPU, 16GB -> Can safely do 25 since Memory = 8GB // ... + LOG(INFO) << "TONYBOOM max_num_qubits=" << max_num_qubits; + for (int i = 0; i < num_samples.size(); ++i) { + for (int j = 0; j < num_samples[i].size(); ++j) { + LOG(INFO) << "TONYBOOM num_samples[" << i << "][" << j << "]=" << num_samples[i][j]; + } + } + for (int i = 0; i < num_qubits.size(); ++i) { + LOG(INFO) << "TONYBOOM num_qubits[" << i << "]=" << num_qubits[i]; + } + for (int i = 0; i < pauli_sums.size(); ++i) { + LOG(INFO) << "TONYBOOM pauli_sum[" << i << "].size()=" << pauli_sums[i].size(); + } + for (int i = 0; i < projector_sums.size(); ++i) { + LOG(INFO) << "TONYBOOM projector_sums[" << i << "].size()=" << projector_sums[i].size(); + } if (max_num_qubits >= 26) { // If the number of qubits is lager than 24, we switch to an // alternate parallelization scheme with runtime: @@ -228,6 +238,7 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { param.normalize_before_mea_gates = true; std::vector unused_stats; // Track op-wise stats. + CHECK_EQ(num_samples[i].size(), pauli_sums[i].size() + projector_sums[i].size()); std::vector run_samples(num_samples[i].size(), 0); std::vector rolling_sums(num_samples[i].size(), 0.0); @@ -250,15 +261,15 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { run_samples[j]++; } for (int j = 0; j < projector_sums[i].size(); j++) { - if (run_samples[j] >= num_samples[i][j]) { + if (run_samples[j + pauli_sums[i].size()] >= num_samples[i][j + pauli_sums[i].size()]) { continue; } float exp_v = 0.0; OP_REQUIRES_OK(context, ComputeExpectationQsim(projector_sums[i][j], sim, ss, sv, scratch, &exp_v)); - rolling_sums[j] += static_cast(exp_v); - run_samples[j]++; + rolling_sums[j + pauli_sums[i].size()] += static_cast(exp_v); + run_samples[j + pauli_sums[i].size()]++; } bool break_loop = true; for (int j = 0; j < num_samples[i].size(); j++) { @@ -334,6 +345,7 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { random_gen.ReserveSamples128(ncircuits.size() * max_n_shots + 1); tensorflow::random::SimplePhilox rand_source(&local_gen); + LOG(INFO) << "TONYBOOM ncircuits.size()=" << ncircuits.size(); for (int i = 0; i < ncircuits.size(); i++) { int nq = num_qubits[i]; int rep_offset = rep_offsets[start][i]; @@ -357,6 +369,8 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { param.normalize_before_mea_gates = true; std::vector unused_stats; // Track op-wise stats. + LOG(INFO) << "TONYBOOM num_samples[" << i << "].size()=" << num_samples[i].size(); + CHECK_EQ(num_samples[i].size(), pauli_sums[i].size() + projector_sums[i].size()); std::vector run_samples(num_samples[i].size(), 0); std::vector rolling_sums(num_samples[i].size(), 0.0); @@ -383,8 +397,8 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { } for (int j = 0; j < projector_sums[i].size(); j++) { - int p_reps = (num_samples[i][j] + num_threads - 1) / num_threads; - if (run_samples[j] >= p_reps + rep_offset) { + int p_reps = (num_samples[i][j + pauli_sums[i].size()] + num_threads - 1) / num_threads; + if (run_samples[j + pauli_sums[i].size()] >= p_reps + rep_offset) { continue; } float exp_v = 0.0; @@ -393,8 +407,8 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { ComputeExpectationQsim(projector_sums[i][j], sim, ss, sv, scratch, &exp_v), c_lock); - rolling_sums[j] += static_cast(exp_v); - run_samples[j]++; + rolling_sums[j + pauli_sums[i].size()] += static_cast(exp_v); + run_samples[j + pauli_sums[i].size()]++; } // Check if we have run enough trajectories for all ops. diff --git a/tensorflow_quantum/core/ops/parse_context.cc b/tensorflow_quantum/core/ops/parse_context.cc index d33321435..8bcfad9cb 100644 --- a/tensorflow_quantum/core/ops/parse_context.cc +++ b/tensorflow_quantum/core/ops/parse_context.cc @@ -419,7 +419,7 @@ tensorflow::Status GetNumSamples( } sub_parsed_num_samples.push_back(num_samples); } - parsed_num_samples->push_back(sub_parsed_num_samples); + parsed_num_samples->emplace_back(sub_parsed_num_samples); } return Status::OK(); diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.cc b/tensorflow_quantum/core/src/circuit_parser_qsim.cc index 8a7b2d490..0f44a3365 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.cc +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.cc @@ -41,6 +41,7 @@ using ::tfq::proto::Moment; using ::tfq::proto::Operation; using ::tfq::proto::PauliTerm; using ::tfq::proto::Program; +using ::tfq::proto::ProjectorTerm; namespace { @@ -450,6 +451,38 @@ inline Status PhasedXGate(const Operation& op, const SymbolMap& param_map, return Status::OK(); } +inline Status MatrixGate1(const Operation& op, const SymbolMap& param_map, + const unsigned int num_qubits, + const unsigned int time, QsimCircuit* circuit, + std::vector* metadata) { + int q0; + bool unused; + //float pexp, pexp_s, exp, exp_s, gs; + Status u; + unused = absl::SimpleAtoi(op.qubits(0).id(), &q0); + + std::vector matrix; + matrix.reserve(8); + + for (const char* param_name : {"x00", "y00", "x01", "y01", "x10", "y10", "x11", "y11"}) { + float param_value; + u = ParseProtoArg(op, param_name, param_map, ¶m_value); + if (!u.ok()) { + return u; + } + matrix.push_back(param_value); + } + auto gate = qsim::Cirq::MatrixGate1::Create( + time, num_qubits - q0 - 1, matrix); + Status s = OptionalInsertControls(op, num_qubits, &gate); + if (!s.ok()) { + return s; + } + circuit->gates.push_back(gate); + + return Status::OK(); +} + // two qubit fsim -> Create(time, q0, q1, theta, phi) inline Status FsimGate(const Operation& op, const SymbolMap& param_map, const unsigned int num_qubits, const unsigned int time, @@ -585,7 +618,8 @@ tensorflow::Status ParseAppendGate(const Operation& op, {"CZP", &CZGate}, {"I2", &I2Gate}, {"CNP", &CXGate}, {"SP", &SwapGate}, {"ISP", &ISwapGate}, {"PXP", &PhasedXGate}, - {"FSIM", &FsimGate}, {"PISP", &PhasedISwapGate}}; + {"FSIM", &FsimGate}, {"PISP", &PhasedISwapGate}, + {"MG1", &MatrixGate1}}; auto build_f = func_map.find(op.gate().id()); if (build_f == func_map.end()) { @@ -885,7 +919,7 @@ Status QsimCircuitFromPauliTerm( // create corresponding eigen gate op. new_op->add_qubits()->set_id(pair.qubit_id()); - new_op->mutable_gate()->set_id(pair.pauli_type() + "P"); + new_op->mutable_gate()->set_id(absl::StrCat(pair.pauli_type(), "P")); (*new_op->mutable_args())["exponent"].mutable_arg_value()->set_float_value( 1.0); (*new_op->mutable_args())["global_shift"] @@ -906,6 +940,38 @@ Status QsimCircuitFromPauliTerm( circuit, fused_circuit); } +Status QsimCircuitFromProjectorTerm( + const ProjectorTerm& term, const int num_qubits, QsimCircuit* circuit, + std::vector>* fused_circuit) { + Program measurement_program; + SymbolMap empty_map; + measurement_program.mutable_circuit()->set_scheduling_strategy( + tfq::proto::Circuit::MOMENT_BY_MOMENT); + Moment* term_moment = measurement_program.mutable_circuit()->add_moments(); + for (const tfq::proto::ProjectorDictEntry& entry : term.projector_dict()) { + Operation* new_op = term_moment->add_operations(); + + // create corresponding eigen gate op. + new_op->add_qubits()->set_id(entry.qubit_id()); + new_op->mutable_gate()->set_id("MG1"); + auto& mutable_args = *new_op->mutable_args(); + mutable_args["x00"].mutable_arg_value()->set_float_value(entry.basis_state() ? 0.0 : 1.0); + mutable_args["y00"].mutable_arg_value()->set_float_value(0.0); + mutable_args["x01"].mutable_arg_value()->set_float_value(0.0); + mutable_args["y01"].mutable_arg_value()->set_float_value(0.0); + mutable_args["x10"].mutable_arg_value()->set_float_value(0.0); + mutable_args["y10"].mutable_arg_value()->set_float_value(0.0); + mutable_args["x11"].mutable_arg_value()->set_float_value(entry.basis_state() ? 1.0 : 0.0); + mutable_args["y11"].mutable_arg_value()->set_float_value(0.0); + + mutable_args["control_values"].mutable_arg_value()->set_string_value(""); + mutable_args["control_qubits"].mutable_arg_value()->set_string_value(""); + } + + return QsimCircuitFromProgram(measurement_program, empty_map, num_qubits, + circuit, fused_circuit); +} + Status QsimZBasisCircuitFromPauliTerm( const PauliTerm& term, const int num_qubits, QsimCircuit* circuit, std::vector>* fused_circuit) { diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.h b/tensorflow_quantum/core/src/circuit_parser_qsim.h index e2966407d..66fc30047 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.h +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.h @@ -27,6 +27,7 @@ limitations under the License. #include "tensorflow/core/lib/core/status.h" #include "tensorflow_quantum/core/proto/pauli_sum.pb.h" #include "tensorflow_quantum/core/proto/program.pb.h" +#include "tensorflow_quantum/core/proto/projector_sum.pb.h" namespace tfq { @@ -93,6 +94,13 @@ tensorflow::Status QsimCircuitFromPauliTerm( qsim::Circuit>* circuit, std::vector>>* fused_circuit); +// parse a serialized projectorTerm from a larger cirq.ProjectorSum proto +// into a qsim Circuit and fused circuit. +tensorflow::Status QsimCircuitFromProjectorTerm( + const tfq::proto::ProjectorTerm& term, const int num_qubits, + qsim::Circuit>* circuit, + std::vector>>* fused_circuit); + // parse a serialized pauliTerm from a larger cirq.Paulisum proto // into a qsim Circuit and fused circuit that represents the transformation // to the z basis. diff --git a/tensorflow_quantum/core/src/util_qsim.h b/tensorflow_quantum/core/src/util_qsim.h index 551f4ce34..7f80408e7 100644 --- a/tensorflow_quantum/core/src/util_qsim.h +++ b/tensorflow_quantum/core/src/util_qsim.h @@ -184,8 +184,39 @@ tensorflow::Status ComputeExpectationQsim(const tfq::proto::ProjectorSum& projec const StateSpaceT& ss, StateT& state, StateT& scratch, float* expectation_value) { - *expectation_value = 0.0; // DO NOT SUBMIT - return tensorflow::Status::OK(); // DO NOT SUBMIT + // apply the gates of the projector terms to a copy of the state vector + // and add up expectation value term by term. + tensorflow::Status status = tensorflow::Status::OK(); + for (const tfq::proto::ProjectorTerm& term : projector_sum.terms()) { + // catch identity terms + if (term.projector_dict_size() == 0) { + *expectation_value += term.coefficient_real(); + // TODO(tonybruguier): error somewhere if identities have any imaginary part + continue; + } + + QsimCircuit main_circuit; + std::vector> fused_circuit; + + status = QsimCircuitFromProjectorTerm(term, state.num_qubits(), &main_circuit, + &fused_circuit); + + if (!status.ok()) { + return status; + } + // copy from src to scratch. + ss.Copy(state, scratch); + for (const qsim::GateFused& fused_gate : fused_circuit) { + qsim::ApplyFusedGate(sim, fused_gate, scratch); + } + + if (!status.ok()) { + return status; + } + *expectation_value += + term.coefficient_real() * ss.RealInnerProduct(state, scratch); + } + return status; } // bad style standards here that we are forced to follow from qsim. diff --git a/tensorflow_quantum/python/util.py b/tensorflow_quantum/python/util.py index 2d58254b5..fd7b142b2 100644 --- a/tensorflow_quantum/python/util.py +++ b/tensorflow_quantum/python/util.py @@ -247,7 +247,7 @@ def random_pauli_sums(qubits, max_sum_length, n_sums): sums = [] paulis = [cirq.I, cirq.X, cirq.Y, cirq.Z] for _ in range(n_sums): - this_sum_length = np.random.randint(1, max_sum_length + 1) + this_sum_length = max_sum_length # np.random.randint(1, max_sum_length + 1) # DO NOT SUBMIT terms = [] for _ in range(this_sum_length): term_length = np.random.randint(1, len(qubits) + 1) From 3178de36bee5c1d85e7ca832c4ce4a00de41b950 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Fri, 26 Nov 2021 04:51:08 +0000 Subject: [PATCH 09/20] intermediate --- .../core/ops/noise/noisy_expectation_op.py | 1 - .../ops/noise/noisy_expectation_op_test.py | 714 +++++++++--------- .../core/ops/noise/tfq_noisy_expectation.cc | 111 ++- .../core/src/circuit_parser_qsim.cc | 31 +- tensorflow_quantum/core/src/util_qsim.h | 16 +- tensorflow_quantum/python/util.py | 2 +- 6 files changed, 435 insertions(+), 440 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py index e7feb01d8..bae6d4806 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py @@ -97,7 +97,6 @@ def expectation(programs, symbol_names, symbol_values, pauli_sums, expectation value for each circuit with each op applied to it (after resolving the corresponding parameters in). """ - print('TONYBOOM noisy_expectation_op()') return NOISY_OP_MODULE.tfq_noisy_expectation( programs, symbol_names, tf.cast(symbol_values, tf.float32), pauli_sums, projector_sums, tf.cast(num_samples, dtype=tf.int32)) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py index 142d71a51..942210ca1 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py @@ -26,287 +26,287 @@ class NoisyExpectationCalculationTest(tf.test.TestCase, parameterized.TestCase): """Tests tfq.noise.expectation.""" - # def test_noisy_expectation_inputs(self): - # """Make sure noisy expectation op fails gracefully on bad inputs.""" - # n_qubits = 5 - # batch_size = 5 - # symbol_names = ['alpha'] - # qubits = cirq.GridQubit.rect(1, n_qubits) - # circuit_batch, resolver_batch = \ - # util.random_symbol_circuit_resolver_batch( - # qubits, symbol_names, batch_size, include_channels=True) - - # symbol_values_array = np.array( - # [[resolver[symbol] - # for symbol in symbol_names] - # for resolver in resolver_batch]) - - # pauli_sums = util.random_pauli_sums(qubits, 3, batch_size) - # projector_sums = util.random_projector_sums(qubits, 3, batch_size) - # num_samples = [[10]] * batch_size - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'programs must be rank 1'): - # # Circuit tensor has too many dimensions. - # noisy_expectation_op.expectation( - # util.convert_to_tensor([circuit_batch]), symbol_names, - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'symbol_names must be rank 1.'): - # # symbol_names tensor has too many dimensions. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), np.array([symbol_names]), - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'symbol_values must be rank 2.'): - # # symbol_values_array tensor has too many dimensions. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # np.array([symbol_values_array]), - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'symbol_values must be rank 2.'): - # # symbol_values_array tensor has too few dimensions. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array[0], - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'pauli_sums must be rank 2.'): - # # pauli_sums tensor has too few dimensions. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, util.convert_to_tensor(list(pauli_sums)), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'projector_sums must be rank 2.'): - # # pauli_sums tensor has too few dimensions. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor(list(projector_sums)), num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'pauli_sums must be rank 2.'): - # # pauli_sums tensor has too many dimensions. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, - # [util.convert_to_tensor([[x] for x in pauli_sums])], - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'projector_sums must be rank 2.'): - # # pauli_sums tensor has too many dimensions. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # [util.convert_to_tensor([[x] for x in projector_sums])], - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'num_samples must be rank 2'): - # # num_samples tensor has the wrong shape. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # [num_samples]) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'num_samples must be rank 2'): - # # num_samples tensor has the wrong shape. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples[0]) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'Unparseable proto'): - # # circuit tensor has the right type but invalid values. - # noisy_expectation_op.expectation( - # ['junk'] * batch_size, symbol_names, symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'Could not find symbol in parameter map'): - # # symbol_names tensor has the right type but invalid values. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), ['junk'], - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'qubits not found in circuit'): - # # pauli_sums tensor has the right type but invalid values. - # new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] - # new_pauli_sums = util.random_pauli_sums(new_qubits, 2, batch_size) - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, - # util.convert_to_tensor([[x] for x in new_pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'qubits not found in circuit'): - # # pauli_sums tensor has the right type but invalid values. - # new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] - # new_projector_sums = util.random_pauli_sums(new_qubits, 2, - # batch_size) - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in new_projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'Unparseable proto'): - # # pauli_sums tensor has the right type but invalid values 2. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, [['junk']] * batch_size, - # [['junk']] * batch_size, num_samples) - - # with self.assertRaisesRegex(TypeError, 'Cannot convert'): - # # circuits tensor has the wrong type. - # noisy_expectation_op.expectation( - # [1.0] * batch_size, symbol_names, symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(TypeError, 'Cannot convert'): - # # symbol_names tensor has the wrong type. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), [0.1234], - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.UnimplementedError, ''): - # # symbol_values tensor has the wrong type. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # [['junk']] * batch_size, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(TypeError, 'Cannot convert'): - # # pauli_sums tensor has the wrong type. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, [[1.0]] * batch_size, - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(TypeError, 'Cannot convert'): - # # pauli_sums tensor has the wrong type. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # [[1.0]] * batch_size, num_samples) - - # with self.assertRaisesRegex(TypeError, 'missing'): - # # we are missing an argument. - # # pylint: disable=no-value-for-parameter - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, num_samples) - # # pylint: enable=no-value-for-parameter - - # with self.assertRaisesRegex(TypeError, 'positional arguments'): - # # pylint: disable=too-many-function-args - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), [], - # num_samples) - # # pylint: enable=too-many-function-args - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # expected_regex='do not match'): - # # wrong op size. - # noisy_expectation_op.expectation( - # util.convert_to_tensor([cirq.Circuit()]), symbol_names, - # symbol_values_array.astype(np.float64), - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # 'greater than 0'): - # # pylint: disable=too-many-function-args - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # [[-1]] * batch_size) - # # pylint: enable=too-many-function-args - - # with self.assertRaisesRegex(tf.errors.InvalidArgumentError, - # expected_regex='do not match'): - # # wrong symbol_values size. - # noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array[:int(batch_size * 0.5)], - # util.convert_to_tensor([[x] for x in pauli_sums]), - # util.convert_to_tensor([[x] for x in projector_sums]), - # num_samples) + def test_noisy_expectation_inputs(self): + """Make sure noisy expectation op fails gracefully on bad inputs.""" + n_qubits = 5 + batch_size = 5 + symbol_names = ['alpha'] + qubits = cirq.GridQubit.rect(1, n_qubits) + circuit_batch, resolver_batch = \ + util.random_symbol_circuit_resolver_batch( + qubits, symbol_names, batch_size, include_channels=True) + + symbol_values_array = np.array( + [[resolver[symbol] + for symbol in symbol_names] + for resolver in resolver_batch]) + + pauli_sums = util.random_pauli_sums(qubits, 3, batch_size) + projector_sums = util.random_projector_sums(qubits, 3, batch_size) + num_samples = [[10, 10]] * batch_size + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'programs must be rank 1'): + # Circuit tensor has too many dimensions. + noisy_expectation_op.expectation( + util.convert_to_tensor([circuit_batch]), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'symbol_names must be rank 1.'): + # symbol_names tensor has too many dimensions. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), np.array([symbol_names]), + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'symbol_values must be rank 2.'): + # symbol_values_array tensor has too many dimensions. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + np.array([symbol_values_array]), + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'symbol_values must be rank 2.'): + # symbol_values_array tensor has too few dimensions. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array[0], + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'pauli_sums must be rank 2.'): + # pauli_sums tensor has too few dimensions. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, util.convert_to_tensor(list(pauli_sums)), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'projector_sums must be rank 2.'): + # pauli_sums tensor has too few dimensions. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor(list(projector_sums)), num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'pauli_sums must be rank 2.'): + # pauli_sums tensor has too many dimensions. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + [util.convert_to_tensor([[x] for x in pauli_sums])], + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'projector_sums must be rank 2.'): + # pauli_sums tensor has too many dimensions. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + [util.convert_to_tensor([[x] for x in projector_sums])], + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'num_samples must be rank 2'): + # num_samples tensor has the wrong shape. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + [num_samples]) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'num_samples must be rank 2'): + # num_samples tensor has the wrong shape. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples[0]) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'Unparseable proto'): + # circuit tensor has the right type but invalid values. + noisy_expectation_op.expectation( + ['junk'] * batch_size, symbol_names, symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'Could not find symbol in parameter map'): + # symbol_names tensor has the right type but invalid values. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), ['junk'], + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'qubits not found in circuit'): + # pauli_sums tensor has the right type but invalid values. + new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] + new_pauli_sums = util.random_pauli_sums(new_qubits, 2, batch_size) + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in new_pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'qubits not found in circuit'): + # pauli_sums tensor has the right type but invalid values. + new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] + new_projector_sums = util.random_pauli_sums(new_qubits, 2, + batch_size) + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in new_projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'Unparseable proto'): + # pauli_sums tensor has the right type but invalid values 2. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, [['junk']] * batch_size, + [['junk']] * batch_size, num_samples) + + with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # circuits tensor has the wrong type. + noisy_expectation_op.expectation( + [1.0] * batch_size, symbol_names, symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # symbol_names tensor has the wrong type. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), [0.1234], + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.UnimplementedError, ''): + # symbol_values tensor has the wrong type. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + [['junk']] * batch_size, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # pauli_sums tensor has the wrong type. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, [[1.0]] * batch_size, + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # pauli_sums tensor has the wrong type. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + [[1.0]] * batch_size, num_samples) + + with self.assertRaisesRegex(TypeError, 'missing'): + # we are missing an argument. + # pylint: disable=no-value-for-parameter + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, num_samples) + # pylint: enable=no-value-for-parameter + + with self.assertRaisesRegex(TypeError, 'positional arguments'): + # pylint: disable=too-many-function-args + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), [], + num_samples) + # pylint: enable=too-many-function-args + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + expected_regex='do not match'): + # wrong op size. + noisy_expectation_op.expectation( + util.convert_to_tensor([cirq.Circuit()]), symbol_names, + symbol_values_array.astype(np.float64), + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'greater than 0'): + # pylint: disable=too-many-function-args + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + [[-1]] * batch_size) + # pylint: enable=too-many-function-args + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + expected_regex='do not match'): + # wrong symbol_values size. + noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array[:int(batch_size * 0.5)], + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) @parameterized.parameters([ - # { - # 'n_qubits': 13, - # 'batch_size': 1, - # 'noisy': False - # }, # ComputeLarge. - # { - # 'n_qubits': 6, - # 'batch_size': 25, - # 'noisy': False - # }, # ComputeSmall. { - 'n_qubits': 5, # DO NOT SUBMIT - 'batch_size': 7, # DO NOT SUBMIT + 'n_qubits': 13, + 'batch_size': 1, + 'noisy': False + }, # ComputeLarge. + { + 'n_qubits': 6, + 'batch_size': 25, + 'noisy': False + }, # ComputeSmall. + { + 'n_qubits': 6, + 'batch_size': 10, 'noisy': True }, # ComputeSmall. - # { - # 'n_qubits': 8, - # 'batch_size': 1, - # 'noisy': True - # } # ComputeLarge. + { + 'n_qubits': 8, + 'batch_size': 1, + 'noisy': True + } # ComputeLarge. ]) def test_simulate_consistency(self, batch_size, n_qubits, noisy): """Test consistency with batch_util.py simulation.""" @@ -330,7 +330,8 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): batch_projector_sums = [ [x, y] for x, y in zip(projector_sums1, projector_sums2) ] - batch_both = [[x, y, z, t] for x, y, z, t in zip(pauli_sums1, pauli_sums2, projector_sums1, projector_sums2)] + batch_both = [[x, y, z, t] for x, y, z, t in zip( + pauli_sums1, pauli_sums2, projector_sums1, projector_sums2)] num_samples = [[10000 if noisy else 3] * 4] * batch_size op_exps = noisy_expectation_op.expectation( @@ -340,89 +341,88 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): cirq_exps = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, batch_both, - cirq.DensityMatrixSimulator() if noisy else cirq.Simulator()) + cirq.DensityMatrixSimulator()) tol = 5e-2 if noisy else 5e-4 - print(f'TONYBOOM op_exps.shape={op_exps.shape}') - print(f'TONYBOOM cirq_exps.shape={cirq_exps.shape}') self.assertAllClose(cirq_exps, op_exps, atol=tol, rtol=tol) - # @parameterized.parameters([{ - # 'channel': x - # } for x in util.get_supported_channels()]) - # def test_single_channel(self, channel): - # """Individually test adding just a single channel type to circuits.""" - # symbol_names = [] - # batch_size = 5 - # n_qubits = 6 - # qubits = cirq.LineQubit.range(n_qubits) - - # circuit_batch, resolver_batch = \ - # util.random_circuit_resolver_batch( - # qubits, batch_size, include_channels=False) - - # for i in range(batch_size): - # circuit_batch[i] = circuit_batch[i] + channel.on_each(*qubits) - - # symbol_values_array = np.array( - # [[resolver[symbol] - # for symbol in symbol_names] - # for resolver in resolver_batch]) - - # pauli_sums1 = util.random_pauli_sums(qubits, 3, batch_size) - # pauli_sums2 = util.random_pauli_sums(qubits, 3, batch_size) - # batch_pauli_sums = [[x, y] for x, y in zip(pauli_sums1, pauli_sums2)] - # projector_sums1 = util.random_projector_sums(qubits, 3, batch_size) - # projector_sums2 = util.random_projector_sums(qubits, 3, batch_size) - # batch_projector_sums = [ - # [x, y] for x, y in zip(projector_sums1, projector_sums2) - # ] - # batch_both = [[x, y, z, t] for x, y, z, t in zip(pauli_sums1, pauli_sums2, projector_sums1, projector_sums2)] - # num_samples = [[10000] * 2] * batch_size - - # op_exps = noisy_expectation_op.expectation( - # util.convert_to_tensor(circuit_batch), symbol_names, - # symbol_values_array, util.convert_to_tensor(batch_pauli_sums), - # util.convert_to_tensor(batch_projector_sums), num_samples) - - # cirq_exps = batch_util.batch_calculate_expectation( - # circuit_batch, resolver_batch, batch_both, - # cirq.DensityMatrixSimulator()) - - # self.assertAllClose(cirq_exps, op_exps, atol=5e-2, rtol=5e-2) - - # def test_correctness_empty(self): - # """Test the expectation for empty circuits.""" - # empty_circuit = util.convert_to_tensor([cirq.Circuit()]) - # empty_symbols = tf.convert_to_tensor([], dtype=tf.dtypes.string) - # empty_values = tf.convert_to_tensor([[]]) - # empty_paulis = tf.convert_to_tensor([[]], dtype=tf.dtypes.string) - # empty_projector_sums = tf.convert_to_tensor([[]], - # dtype=tf.dtypes.string) - # empty_n_samples = tf.convert_to_tensor([[]], dtype=tf.int32) - - # out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, - # empty_values, empty_paulis, - # empty_projector_sums, - # empty_n_samples) - - # expected = np.array([[]], dtype=np.complex64) - # self.assertAllClose(out, expected) - - # def test_correctness_no_circuit(self): - # """Test the correctness with the empty tensor.""" - # empty_circuit = tf.raw_ops.Empty(shape=(0,), dtype=tf.string) - # empty_symbols = tf.raw_ops.Empty(shape=(0,), dtype=tf.string) - # empty_values = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.float32) - # empty_paulis = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) - # empty_projector_sums = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) - # empty_n_samples = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.int32) - - # out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, - # empty_values, empty_paulis, - # empty_projector_sums, - # empty_n_samples) - - # self.assertShapeEqual(np.zeros((0, 0)), out) + @parameterized.parameters([{ + 'channel': x + } for x in util.get_supported_channels()]) + def test_single_channel(self, channel): + """Individually test adding just a single channel type to circuits.""" + symbol_names = [] + batch_size = 5 + n_qubits = 6 + qubits = cirq.LineQubit.range(n_qubits) + + circuit_batch, resolver_batch = \ + util.random_circuit_resolver_batch( + qubits, batch_size, include_channels=False) + + for i in range(batch_size): + circuit_batch[i] = circuit_batch[i] + channel.on_each(*qubits) + + symbol_values_array = np.array( + [[resolver[symbol] + for symbol in symbol_names] + for resolver in resolver_batch]) + + pauli_sums1 = util.random_pauli_sums(qubits, 3, batch_size) + pauli_sums2 = util.random_pauli_sums(qubits, 3, batch_size) + batch_pauli_sums = [[x, y] for x, y in zip(pauli_sums1, pauli_sums2)] + projector_sums1 = util.random_projector_sums(qubits, 3, batch_size) + projector_sums2 = util.random_projector_sums(qubits, 3, batch_size) + batch_projector_sums = [ + [x, y] for x, y in zip(projector_sums1, projector_sums2) + ] + batch_both = [[x, y, z, t] for x, y, z, t in zip( + pauli_sums1, pauli_sums2, projector_sums1, projector_sums2)] + num_samples = [[10000] * 4] * batch_size + + op_exps = noisy_expectation_op.expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, util.convert_to_tensor(batch_pauli_sums), + util.convert_to_tensor(batch_projector_sums), num_samples) + + cirq_exps = batch_util.batch_calculate_expectation( + circuit_batch, resolver_batch, batch_both, + cirq.DensityMatrixSimulator()) + + self.assertAllClose(cirq_exps, op_exps, atol=5e-2, rtol=5e-2) + + def test_correctness_empty(self): + """Test the expectation for empty circuits.""" + empty_circuit = util.convert_to_tensor([cirq.Circuit()]) + empty_symbols = tf.convert_to_tensor([], dtype=tf.dtypes.string) + empty_values = tf.convert_to_tensor([[]]) + empty_paulis = tf.convert_to_tensor([[]], dtype=tf.dtypes.string) + empty_projector_sums = tf.convert_to_tensor([[]], + dtype=tf.dtypes.string) + empty_n_samples = tf.convert_to_tensor([[]], dtype=tf.int32) + + out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, + empty_values, empty_paulis, + empty_projector_sums, + empty_n_samples) + + expected = np.array([[]], dtype=np.complex64) + self.assertAllClose(out, expected) + + def test_correctness_no_circuit(self): + """Test the correctness with the empty tensor.""" + empty_circuit = tf.raw_ops.Empty(shape=(0,), dtype=tf.string) + empty_symbols = tf.raw_ops.Empty(shape=(0,), dtype=tf.string) + empty_values = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.float32) + empty_paulis = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) + empty_projector_sums = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) + empty_n_samples = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.int32) + + out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, + empty_values, empty_paulis, + empty_projector_sums, + empty_n_samples) + + self.assertShapeEqual(np.zeros((0, 0)), out) if __name__ == "__main__": diff --git a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc index 9ebf288df..33308d1f3 100644 --- a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc +++ b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc @@ -68,9 +68,8 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { // Create the output Tensor. const int output_dim_batch_size = context->input(0).dim_size(0); - const int output_dim_op_size = context->input(3).dim_size(1) + context->input(4).dim_size(1); - LOG(INFO) << "TONYBOOM output_dim_batch_size=" << output_dim_batch_size; - LOG(INFO) << "TONYBOOM output_dim_op_size=" << output_dim_op_size; + const int output_dim_op_size = + context->input(3).dim_size(1) + context->input(4).dim_size(1); tensorflow::TensorShape output_shape; output_shape.AddDim(output_dim_batch_size); output_shape.AddDim(output_dim_op_size); @@ -105,19 +104,23 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { "Got ", num_samples.size(), " lists of sample sizes and ", pauli_sums.size(), " lists of pauli sums."))); - OP_REQUIRES(context, num_samples.size() == projector_sums.size(), - tensorflow::errors::InvalidArgument(absl::StrCat( - "Dimension 0 of num_samples and projector_sums do not match.", - "Got ", num_samples.size(), " lists of sample sizes and ", - projector_sums.size(), " lists of projector sums."))); + OP_REQUIRES( + context, num_samples.size() == projector_sums.size(), + tensorflow::errors::InvalidArgument(absl::StrCat( + "Dimension 0 of num_samples and projector_sums do not match.", + "Got ", num_samples.size(), " lists of sample sizes and ", + projector_sums.size(), " lists of projector sums."))); OP_REQUIRES( - context, context->input(5).dim_size(1) == context->input(3).dim_size(1) + context->input(4).dim_size(1), + context, + context->input(5).dim_size(1) == + context->input(3).dim_size(1) + context->input(4).dim_size(1), tensorflow::errors::InvalidArgument(absl::StrCat( - "Dimension 1 of num_samples and pauli_sums + projector_sums do not match.", "Got ", - context->input(5).dim_size(1), " lists of sample sizes and ", - context->input(3).dim_size(1), " lists of pauli sums and ", - context->input(4).dim_size(1), " lists of projector sums."))); + "Dimension 1 of num_samples and pauli_sums + projector_sums do ", + "not match. Got ", context->input(5).dim_size(1), + " lists of sample sizes and ", context->input(3).dim_size(1), + " lists of pauli sums and ", context->input(4).dim_size(1), + " lists of projector sums."))); // Construct qsim circuits. std::vector qsim_circuits(programs.size(), @@ -148,44 +151,30 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { // e2s2 = 2 CPU, 8GB -> Can safely do 25 since Memory = 4GB // e2s4 = 4 CPU, 16GB -> Can safely do 25 since Memory = 8GB // ... - LOG(INFO) << "TONYBOOM max_num_qubits=" << max_num_qubits; - for (int i = 0; i < num_samples.size(); ++i) { - for (int j = 0; j < num_samples[i].size(); ++j) { - LOG(INFO) << "TONYBOOM num_samples[" << i << "][" << j << "]=" << num_samples[i][j]; - } - } - for (int i = 0; i < num_qubits.size(); ++i) { - LOG(INFO) << "TONYBOOM num_qubits[" << i << "]=" << num_qubits[i]; - } - for (int i = 0; i < pauli_sums.size(); ++i) { - LOG(INFO) << "TONYBOOM pauli_sum[" << i << "].size()=" << pauli_sums[i].size(); - } - for (int i = 0; i < projector_sums.size(); ++i) { - LOG(INFO) << "TONYBOOM projector_sums[" << i << "].size()=" << projector_sums[i].size(); - } if (max_num_qubits >= 26) { // If the number of qubits is lager than 24, we switch to an // alternate parallelization scheme with runtime: // O(n_circuits * max_j(num_samples[i])) with parallelization being // multiple threads per wavefunction. - ComputeLarge(num_qubits, qsim_circuits, pauli_sums, projector_sums, num_samples, context, - &output_tensor); + ComputeLarge(num_qubits, qsim_circuits, pauli_sums, projector_sums, + num_samples, context, &output_tensor); } else { // Runtime: O(n_circuits * max_j(num_samples[i])) with parallelization // being done over number of trials. - ComputeSmall(num_qubits, max_num_qubits, qsim_circuits, pauli_sums, projector_sums, - num_samples, context, &output_tensor); + ComputeSmall(num_qubits, max_num_qubits, qsim_circuits, pauli_sums, + projector_sums, num_samples, context, &output_tensor); } } private: - void ComputeLarge(const std::vector& num_qubits, - const std::vector& ncircuits, - const std::vector>& pauli_sums, - const std::vector>& projector_sums, - const std::vector>& num_samples, - tensorflow::OpKernelContext* context, - tensorflow::TTypes::Matrix* output_tensor) { + void ComputeLarge( + const std::vector& num_qubits, + const std::vector& ncircuits, + const std::vector>& pauli_sums, + const std::vector>& projector_sums, + const std::vector>& num_samples, + tensorflow::OpKernelContext* context, + tensorflow::TTypes::Matrix* output_tensor) { // Instantiate qsim objects. const auto tfq_for = tfq::QsimFor(context); using Simulator = qsim::Simulator; @@ -238,7 +227,8 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { param.normalize_before_mea_gates = true; std::vector unused_stats; // Track op-wise stats. - CHECK_EQ(num_samples[i].size(), pauli_sums[i].size() + projector_sums[i].size()); + CHECK_EQ(num_samples[i].size(), + pauli_sums[i].size() + projector_sums[i].size()); std::vector run_samples(num_samples[i].size(), 0); std::vector rolling_sums(num_samples[i].size(), 0.0); @@ -261,13 +251,14 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { run_samples[j]++; } for (int j = 0; j < projector_sums[i].size(); j++) { - if (run_samples[j + pauli_sums[i].size()] >= num_samples[i][j + pauli_sums[i].size()]) { + if (run_samples[j + pauli_sums[i].size()] >= + num_samples[i][j + pauli_sums[i].size()]) { continue; } float exp_v = 0.0; - OP_REQUIRES_OK(context, - ComputeExpectationQsim(projector_sums[i][j], sim, ss, sv, - scratch, &exp_v)); + OP_REQUIRES_OK( + context, ComputeExpectationQsim(projector_sums[i][j], sim, ss, sv, + scratch, &exp_v)); rolling_sums[j + pauli_sums[i].size()] += static_cast(exp_v); run_samples[j + pauli_sums[i].size()]++; } @@ -289,14 +280,14 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { } } - void ComputeSmall(const std::vector& num_qubits, - const int max_num_qubits, - const std::vector& ncircuits, - const std::vector>& pauli_sums, - const std::vector>& projector_sums, - const std::vector>& num_samples, - tensorflow::OpKernelContext* context, - tensorflow::TTypes::Matrix* output_tensor) { + void ComputeSmall( + const std::vector& num_qubits, const int max_num_qubits, + const std::vector& ncircuits, + const std::vector>& pauli_sums, + const std::vector>& projector_sums, + const std::vector>& num_samples, + tensorflow::OpKernelContext* context, + tensorflow::TTypes::Matrix* output_tensor) { using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; using QTSimulator = @@ -345,7 +336,6 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { random_gen.ReserveSamples128(ncircuits.size() * max_n_shots + 1); tensorflow::random::SimplePhilox rand_source(&local_gen); - LOG(INFO) << "TONYBOOM ncircuits.size()=" << ncircuits.size(); for (int i = 0; i < ncircuits.size(); i++) { int nq = num_qubits[i]; int rep_offset = rep_offsets[start][i]; @@ -369,8 +359,8 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { param.normalize_before_mea_gates = true; std::vector unused_stats; // Track op-wise stats. - LOG(INFO) << "TONYBOOM num_samples[" << i << "].size()=" << num_samples[i].size(); - CHECK_EQ(num_samples[i].size(), pauli_sums[i].size() + projector_sums[i].size()); + CHECK_EQ(num_samples[i].size(), + pauli_sums[i].size() + projector_sums[i].size()); std::vector run_samples(num_samples[i].size(), 0); std::vector rolling_sums(num_samples[i].size(), 0.0); @@ -397,17 +387,20 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { } for (int j = 0; j < projector_sums[i].size(); j++) { - int p_reps = (num_samples[i][j + pauli_sums[i].size()] + num_threads - 1) / num_threads; + int p_reps = + (num_samples[i][j + pauli_sums[i].size()] + num_threads - 1) / + num_threads; if (run_samples[j + pauli_sums[i].size()] >= p_reps + rep_offset) { continue; } float exp_v = 0.0; NESTED_FN_STATUS_SYNC( compute_status, - ComputeExpectationQsim(projector_sums[i][j], sim, ss, sv, scratch, - &exp_v), + ComputeExpectationQsim(projector_sums[i][j], sim, ss, sv, + scratch, &exp_v), c_lock); - rolling_sums[j + pauli_sums[i].size()] += static_cast(exp_v); + rolling_sums[j + pauli_sums[i].size()] += + static_cast(exp_v); run_samples[j + pauli_sums[i].size()]++; } diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.cc b/tensorflow_quantum/core/src/circuit_parser_qsim.cc index 0f44a3365..352f1f5f8 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.cc +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.cc @@ -457,14 +457,15 @@ inline Status MatrixGate1(const Operation& op, const SymbolMap& param_map, std::vector* metadata) { int q0; bool unused; - //float pexp, pexp_s, exp, exp_s, gs; + // float pexp, pexp_s, exp, exp_s, gs; Status u; unused = absl::SimpleAtoi(op.qubits(0).id(), &q0); std::vector matrix; matrix.reserve(8); - for (const char* param_name : {"x00", "y00", "x01", "y01", "x10", "y10", "x11", "y11"}) { + for (const char* param_name : + {"x00", "y00", "x01", "y01", "x10", "y10", "x11", "y11"}) { float param_value; u = ParseProtoArg(op, param_name, param_map, ¶m_value); if (!u.ok()) { @@ -472,8 +473,8 @@ inline Status MatrixGate1(const Operation& op, const SymbolMap& param_map, } matrix.push_back(param_value); } - auto gate = qsim::Cirq::MatrixGate1::Create( - time, num_qubits - q0 - 1, matrix); + auto gate = + qsim::Cirq::MatrixGate1::Create(time, num_qubits - q0 - 1, matrix); Status s = OptionalInsertControls(op, num_qubits, &gate); if (!s.ok()) { return s; @@ -611,14 +612,14 @@ tensorflow::Status ParseAppendGate(const Operation& op, std::function*)>> - func_map = {{"I", &IGate}, {"HP", &HGate}, - {"XP", &XGate}, {"XXP", &XXGate}, - {"YP", &YGate}, {"YYP", &YYGate}, - {"ZP", &ZGate}, {"ZZP", &ZZGate}, - {"CZP", &CZGate}, {"I2", &I2Gate}, - {"CNP", &CXGate}, {"SP", &SwapGate}, - {"ISP", &ISwapGate}, {"PXP", &PhasedXGate}, - {"FSIM", &FsimGate}, {"PISP", &PhasedISwapGate}, + func_map = {{"I", &IGate}, {"HP", &HGate}, + {"XP", &XGate}, {"XXP", &XXGate}, + {"YP", &YGate}, {"YYP", &YYGate}, + {"ZP", &ZGate}, {"ZZP", &ZZGate}, + {"CZP", &CZGate}, {"I2", &I2Gate}, + {"CNP", &CXGate}, {"SP", &SwapGate}, + {"ISP", &ISwapGate}, {"PXP", &PhasedXGate}, + {"FSIM", &FsimGate}, {"PISP", &PhasedISwapGate}, {"MG1", &MatrixGate1}}; auto build_f = func_map.find(op.gate().id()); @@ -955,13 +956,15 @@ Status QsimCircuitFromProjectorTerm( new_op->add_qubits()->set_id(entry.qubit_id()); new_op->mutable_gate()->set_id("MG1"); auto& mutable_args = *new_op->mutable_args(); - mutable_args["x00"].mutable_arg_value()->set_float_value(entry.basis_state() ? 0.0 : 1.0); + mutable_args["x00"].mutable_arg_value()->set_float_value( + entry.basis_state() ? 0.0 : 1.0); mutable_args["y00"].mutable_arg_value()->set_float_value(0.0); mutable_args["x01"].mutable_arg_value()->set_float_value(0.0); mutable_args["y01"].mutable_arg_value()->set_float_value(0.0); mutable_args["x10"].mutable_arg_value()->set_float_value(0.0); mutable_args["y10"].mutable_arg_value()->set_float_value(0.0); - mutable_args["x11"].mutable_arg_value()->set_float_value(entry.basis_state() ? 1.0 : 0.0); + mutable_args["x11"].mutable_arg_value()->set_float_value( + entry.basis_state() ? 1.0 : 0.0); mutable_args["y11"].mutable_arg_value()->set_float_value(0.0); mutable_args["control_values"].mutable_arg_value()->set_string_value(""); diff --git a/tensorflow_quantum/core/src/util_qsim.h b/tensorflow_quantum/core/src/util_qsim.h index 7f80408e7..92d39d005 100644 --- a/tensorflow_quantum/core/src/util_qsim.h +++ b/tensorflow_quantum/core/src/util_qsim.h @@ -179,11 +179,10 @@ tensorflow::Status ComputeExpectationQsim(const tfq::proto::PauliSum& p_sum, } template -tensorflow::Status ComputeExpectationQsim(const tfq::proto::ProjectorSum& projector_sum, - const SimT& sim, - const StateSpaceT& ss, StateT& state, - StateT& scratch, - float* expectation_value) { +tensorflow::Status ComputeExpectationQsim( + const tfq::proto::ProjectorSum& projector_sum, const SimT& sim, + const StateSpaceT& ss, StateT& state, StateT& scratch, + float* expectation_value) { // apply the gates of the projector terms to a copy of the state vector // and add up expectation value term by term. tensorflow::Status status = tensorflow::Status::OK(); @@ -191,15 +190,16 @@ tensorflow::Status ComputeExpectationQsim(const tfq::proto::ProjectorSum& projec // catch identity terms if (term.projector_dict_size() == 0) { *expectation_value += term.coefficient_real(); - // TODO(tonybruguier): error somewhere if identities have any imaginary part + // TODO(tonybruguier): error somewhere if identities have any imaginary + // part continue; } QsimCircuit main_circuit; std::vector> fused_circuit; - status = QsimCircuitFromProjectorTerm(term, state.num_qubits(), &main_circuit, - &fused_circuit); + status = QsimCircuitFromProjectorTerm(term, state.num_qubits(), + &main_circuit, &fused_circuit); if (!status.ok()) { return status; diff --git a/tensorflow_quantum/python/util.py b/tensorflow_quantum/python/util.py index fd7b142b2..2d58254b5 100644 --- a/tensorflow_quantum/python/util.py +++ b/tensorflow_quantum/python/util.py @@ -247,7 +247,7 @@ def random_pauli_sums(qubits, max_sum_length, n_sums): sums = [] paulis = [cirq.I, cirq.X, cirq.Y, cirq.Z] for _ in range(n_sums): - this_sum_length = max_sum_length # np.random.randint(1, max_sum_length + 1) # DO NOT SUBMIT + this_sum_length = np.random.randint(1, max_sum_length + 1) terms = [] for _ in range(this_sum_length): term_length = np.random.randint(1, len(qubits) + 1) From af44e0afe26d2f1488d2389ba9aa3d2fb69522b3 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Sat, 27 Nov 2021 14:00:10 +0000 Subject: [PATCH 10/20] intermediate --- .../ops/noise/noisy_sampled_expectation_op.py | 6 +- .../noisy_sampled_expectation_op_test.py | 155 +++++++++++++++--- .../noise/tfq_noisy_sampled_expectation.cc | 86 ++++++++-- .../core/src/circuit_parser_qsim.cc | 47 ++++++ .../core/src/circuit_parser_qsim.h | 8 + tensorflow_quantum/core/src/util_qsim.h | 82 +++++++++ 6 files changed, 347 insertions(+), 37 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py index 1874c8a5e..9c4f3cb15 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py @@ -20,7 +20,7 @@ NOISY_OP_MODULE = load_module(os.path.join("noise", "_tfq_noise_ops.so")) -def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, +def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, projector_sums, num_samples): """Estimates (via sampling) expectation values using monte-carlo simulation. @@ -79,6 +79,9 @@ def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, pauli_sums: `tf.Tensor` of strings with shape [batch_size, n_ops] containing the string representation of the operators that will be used on all of the circuits in the expectation calculations. + projector_sums: `tf.Tensor` of strings with shape [batch_size, n_ops] + containing the string representation of the operators that will + be used on all of the circuits in the expectation calculations. num_samples: `tf.Tensor` with `num_samples[i][j]` is equal to the number of times `programs[i]` will be simulated to estimate `pauli_sums[i][j]`. Therefore, `num_samples` must have the same @@ -94,4 +97,5 @@ def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, """ return NOISY_OP_MODULE.tfq_noisy_sampled_expectation( programs, symbol_names, tf.cast(symbol_values, tf.float32), pauli_sums, + projector_sums, tf.cast(num_samples, dtype=tf.int32)) diff --git a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py index 98d25d554..6281bc18e 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py @@ -42,7 +42,8 @@ def test_noisy_expectation_inputs(self): for resolver in resolver_batch]) pauli_sums = util.random_pauli_sums(qubits, 3, batch_size) - num_samples = [[10]] * batch_size + projector_sums = util.random_projector_sums(qubits, 3, batch_size) + num_samples = [[10, 10]] * batch_size with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'programs must be rank 1'): @@ -50,7 +51,9 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor([circuit_batch]), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_names must be rank 1.'): @@ -58,7 +61,9 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), np.array([symbol_names]), symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_values must be rank 2.'): @@ -66,7 +71,9 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, np.array([symbol_values_array]), - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_values must be rank 2.'): @@ -74,7 +81,9 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array[0], - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'pauli_sums must be rank 2.'): @@ -82,7 +91,19 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor(list(pauli_sums)), num_samples) + util.convert_to_tensor(list(pauli_sums)), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'projector_sums must be rank 2.'): + # pauli_sums tensor has too few dimensions. + noisy_sampled_expectation_op.sampled_expectation( + util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor(list(projector_sums)), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'pauli_sums must be rank 2.'): @@ -91,6 +112,17 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, [util.convert_to_tensor([[x] for x in pauli_sums])], + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'projector_sums must be rank 2.'): + # pauli_sums tensor has too many dimensions. + noisy_sampled_expectation_op.sampled_expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + [util.convert_to_tensor([[x] for x in projector_sums])], num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -100,6 +132,7 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), [num_samples]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -109,6 +142,7 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), num_samples[0]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -116,7 +150,9 @@ def test_noisy_expectation_inputs(self): # circuit tensor has the right type but invalid values. noisy_sampled_expectation_op.sampled_expectation( ['junk'] * batch_size, symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Could not find symbol in parameter map'): @@ -124,7 +160,9 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), ['junk'], symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'qubits not found in circuit'): @@ -135,6 +173,29 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in new_pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'qubits not found in circuit'): + # pauli_sums tensor has the right type but invalid values. + new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] + new_projector_sums = util.random_projector_sums(new_qubits, 2, batch_size) + noisy_sampled_expectation_op.sampled_expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in new_projector_sums]), + num_samples) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'Unparseable proto'): + # pauli_sums tensor has the right type but invalid values 2. + noisy_sampled_expectation_op.sampled_expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, + [['junk']] * batch_size, + util.convert_to_tensor([[x] for x in new_projector_sums]), num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -142,33 +203,52 @@ def test_noisy_expectation_inputs(self): # pauli_sums tensor has the right type but invalid values 2. noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [['junk']] * batch_size, num_samples) + symbol_values_array, + util.convert_to_tensor([[x] for x in pauli_sums]), + [['junk']] * batch_size, + num_samples) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # circuits tensor has the wrong type. noisy_sampled_expectation_op.sampled_expectation( [1.0] * batch_size, symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # symbol_names tensor has the wrong type. noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), [0.1234], symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.UnimplementedError, ''): # symbol_values tensor has the wrong type. noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, [['junk']] * batch_size, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # pauli_sums tensor has the wrong type. noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [[1.0]] * batch_size, num_samples) + symbol_values_array, [[1.0]] * batch_size, + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) + + with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # projector_sums tensor has the wrong type. + noisy_sampled_expectation_op.sampled_expectation( + util.convert_to_tensor(circuit_batch), symbol_names, + util.convert_to_tensor([[x] for x in pauli_sums]), + symbol_values_array, [[1.0]] * batch_size, + num_samples) with self.assertRaisesRegex(TypeError, 'missing'): # we are missing an argument. @@ -183,7 +263,8 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), [], + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), [], num_samples) # pylint: enable=too-many-function-args @@ -193,7 +274,9 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor([cirq.Circuit()]), symbol_names, symbol_values_array.astype(np.float64), - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'greater than 0'): @@ -202,6 +285,7 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), [[-1]] * batch_size) # pylint: enable=too-many-function-args @@ -211,7 +295,9 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array[:int(batch_size * 0.5)], - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples) @parameterized.parameters([ { @@ -252,16 +338,24 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): pauli_sums1 = util.random_pauli_sums(qubits, 3, batch_size) pauli_sums2 = util.random_pauli_sums(qubits, 3, batch_size) batch_pauli_sums = [[x, y] for x, y in zip(pauli_sums1, pauli_sums2)] - num_samples = [[10000] * 2] * batch_size + projector_sums1 = util.random_projector_sums(qubits, 3, batch_size) + projector_sums2 = util.random_projector_sums(qubits, 3, batch_size) + batch_projector_sums = [ + [x, y] for x, y in zip(projector_sums1, projector_sums2) + ] + batch_both = [[x, y, z, t] for x, y, z, t in zip( + pauli_sums1, pauli_sums2, projector_sums1, projector_sums2)] + num_samples = [[10000] * 4] * batch_size op_exps = noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor(batch_pauli_sums), num_samples) + util.convert_to_tensor(batch_pauli_sums), + util.convert_to_tensor(batch_projector_sums), num_samples) cirq_exps = batch_util.batch_calculate_expectation( - circuit_batch, resolver_batch, batch_pauli_sums, - cirq.DensityMatrixSimulator() if noisy else cirq.Simulator()) + circuit_batch, resolver_batch, batch_both, + cirq.DensityMatrixSimulator()) tol = 0.5 self.assertAllClose(cirq_exps, op_exps, atol=tol, rtol=tol) @@ -290,15 +384,24 @@ def test_single_channel(self, channel): pauli_sums1 = util.random_pauli_sums(qubits, 3, batch_size) pauli_sums2 = util.random_pauli_sums(qubits, 3, batch_size) batch_pauli_sums = [[x, y] for x, y in zip(pauli_sums1, pauli_sums2)] - num_samples = [[20000] * 2] * batch_size + projector_sums1 = util.random_projector_sums(qubits, 3, batch_size) + projector_sums2 = util.random_projector_sums(qubits, 3, batch_size) + batch_projector_sums = [ + [x, y] for x, y in zip(projector_sums1, projector_sums2) + ] + batch_both = [[x, y, z, t] for x, y, z, t in zip( + pauli_sums1, pauli_sums2, projector_sums1, projector_sums2)] + num_samples = [[20000] * 4] * batch_size op_exps = noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor(batch_pauli_sums), num_samples) + util.convert_to_tensor(batch_pauli_sums), + util.convert_to_tensor(batch_projector_sums), + num_samples) cirq_exps = batch_util.batch_calculate_expectation( - circuit_batch, resolver_batch, batch_pauli_sums, + circuit_batch, resolver_batch, batch_both, cirq.DensityMatrixSimulator()) self.assertAllClose(cirq_exps, op_exps, atol=0.35, rtol=0.35) @@ -309,10 +412,11 @@ def test_correctness_empty(self): empty_symbols = tf.convert_to_tensor([], dtype=tf.dtypes.string) empty_values = tf.convert_to_tensor([[]]) empty_paulis = tf.convert_to_tensor([[]], dtype=tf.dtypes.string) + empty_projectors = tf.convert_to_tensor([[]], dtype=tf.dtypes.string) empty_n_samples = tf.convert_to_tensor([[]], dtype=tf.int32) out = noisy_sampled_expectation_op.sampled_expectation( - empty_circuit, empty_symbols, empty_values, empty_paulis, + empty_circuit, empty_symbols, empty_values, empty_paulis, empty_projectors, empty_n_samples) expected = np.array([[]], dtype=np.complex64) @@ -324,10 +428,11 @@ def test_correctness_no_circuit(self): empty_symbols = tf.raw_ops.Empty(shape=(0,), dtype=tf.string) empty_values = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.float32) empty_paulis = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) + empty_projectors = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) empty_n_samples = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.int32) out = noisy_sampled_expectation_op.sampled_expectation( - empty_circuit, empty_symbols, empty_values, empty_paulis, + empty_circuit, empty_symbols, empty_values, empty_paulis, empty_projectors, empty_n_samples) self.assertShapeEqual(np.zeros((0, 0)), out) diff --git a/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc b/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc index 77d6197ae..7bb1d8a9a 100644 --- a/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc +++ b/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc @@ -41,6 +41,7 @@ limitations under the License. #include "tensorflow_quantum/core/ops/parse_context.h" #include "tensorflow_quantum/core/proto/pauli_sum.pb.h" #include "tensorflow_quantum/core/proto/program.pb.h" +#include "tensorflow_quantum/core/proto/projector_sum.pb.h" #include "tensorflow_quantum/core/src/util_qsim.h" namespace tfq { @@ -48,6 +49,7 @@ namespace tfq { using ::tensorflow::Status; using ::tfq::proto::PauliSum; using ::tfq::proto::Program; +using ::tfq::proto::ProjectorSum; typedef qsim::Cirq::GateCirq QsimGate; typedef qsim::Circuit QsimCircuit; @@ -62,13 +64,14 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { void Compute(tensorflow::OpKernelContext* context) override { // TODO (mbbrough): add more dimension checks for other inputs here. const int num_inputs = context->num_inputs(); - OP_REQUIRES(context, num_inputs == 5, + OP_REQUIRES(context, num_inputs == 6, tensorflow::errors::InvalidArgument(absl::StrCat( - "Expected 5 inputs, got ", num_inputs, " inputs."))); + "Expected 6 inputs, got ", num_inputs, " inputs."))); // Create the output Tensor. const int output_dim_batch_size = context->input(0).dim_size(0); - const int output_dim_op_size = context->input(3).dim_size(1); + const int output_dim_op_size = + context->input(3).dim_size(1) + context->input(4).dim_size(1); tensorflow::TensorShape output_shape; output_shape.AddDim(output_dim_batch_size); output_shape.AddDim(output_dim_op_size); @@ -80,8 +83,9 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { std::vector programs; std::vector num_qubits; std::vector> pauli_sums; + std::vector> projector_sums; OP_REQUIRES_OK(context, GetProgramsAndNumQubits(context, &programs, - &num_qubits, &pauli_sums)); + &num_qubits, &pauli_sums, &projector_sums)); std::vector maps; OP_REQUIRES_OK(context, GetSymbolMaps(context, &maps)); @@ -102,11 +106,22 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { pauli_sums.size(), " lists of pauli sums."))); OP_REQUIRES( - context, context->input(4).dim_size(1) == context->input(3).dim_size(1), + context, num_samples.size() == projector_sums.size(), tensorflow::errors::InvalidArgument(absl::StrCat( - "Dimension 1 of num_samples and pauli_sums do not match.", "Got ", - context->input(4).dim_size(1), " lists of sample sizes and ", - context->input(3).dim_size(1), " lists of pauli sums."))); + "Dimension 0 of num_samples and projector_sums do not match.", + "Got ", num_samples.size(), " lists of sample sizes and ", + projector_sums.size(), " lists of projector sums."))); + + OP_REQUIRES( + context, + context->input(5).dim_size(1) == + context->input(3).dim_size(1) + context->input(4).dim_size(1), + tensorflow::errors::InvalidArgument(absl::StrCat( + "Dimension 1 of num_samples and pauli_sums + projector_sums do ", + "not match. Got ", context->input(5).dim_size(1), + " lists of sample sizes and ", context->input(3).dim_size(1), + " lists of pauli sums and ", context->input(4).dim_size(1), + " lists of projector sums."))); // Construct qsim circuits. std::vector qsim_circuits(programs.size(), @@ -142,12 +157,12 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { // alternate parallelization scheme with runtime: // O(n_circuits * max_j(num_samples[i])) with parallelization being // multiple threads per wavefunction. - ComputeLarge(num_qubits, qsim_circuits, pauli_sums, num_samples, context, + ComputeLarge(num_qubits, qsim_circuits, pauli_sums, projector_sums, num_samples, context, &output_tensor); } else { // Runtime: O(n_circuits * max_j(num_samples[i])) with parallelization // being done over number of trials. - ComputeSmall(num_qubits, max_num_qubits, qsim_circuits, pauli_sums, + ComputeSmall(num_qubits, max_num_qubits, qsim_circuits, pauli_sums, projector_sums, num_samples, context, &output_tensor); } } @@ -156,6 +171,7 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { void ComputeLarge(const std::vector& num_qubits, const std::vector& ncircuits, const std::vector>& pauli_sums, + const std::vector>& projector_sums, const std::vector>& num_samples, tensorflow::OpKernelContext* context, tensorflow::TTypes::Matrix* output_tensor) { @@ -184,6 +200,13 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { max_n_shots = std::max(max_n_shots, num_samples[i][j]); } } + for (int i = 0; i < projector_sums.size(); i++) { + for (int j = 0; j < projector_sums[i].size(); j++) { + max_psum_length = + std::max(max_psum_length, projector_sums[i][j].terms().size()); + max_n_shots = std::max(max_n_shots, num_samples[i][j + pauli_sums[i].size()]); + } + } random_gen.Init(tensorflow::random::New64(), tensorflow::random::New64()); auto local_gen = random_gen.ReserveSamples128( ncircuits.size() * (1 + max_psum_length) * max_n_shots); @@ -214,6 +237,8 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { param.normalize_before_mea_gates = true; std::vector unused_stats; // Track op-wise stats. + CHECK_EQ(num_samples[i].size(), + pauli_sums[i].size() + projector_sums[i].size()); std::vector run_samples(num_samples[i].size(), 0); std::vector rolling_sums(num_samples[i].size(), 0.0); @@ -235,6 +260,17 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { rolling_sums[j] += static_cast(exp_v); run_samples[j]++; } + for (int j = 0; j < projector_sums[i].size(); j++) { + if (run_samples[j + pauli_sums[i].size()] >= num_samples[i][j + pauli_sums[i].size()]) { + continue; + } + float exp_v = 0.0; + OP_REQUIRES_OK(context, ComputeSampledExpectationQsim( + projector_sums[i][j], sim, ss, sv, scratch, 1, + rand_source, &exp_v)); + rolling_sums[j + pauli_sums[i].size()] += static_cast(exp_v); + run_samples[j + pauli_sums[i].size()]++; + } bool break_loop = true; for (int j = 0; j < num_samples[i].size(); j++) { if (run_samples[j] < num_samples[i][j]) { @@ -257,6 +293,7 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { const int max_num_qubits, const std::vector& ncircuits, const std::vector>& pauli_sums, + const std::vector>& projector_sums, const std::vector>& num_samples, tensorflow::OpKernelContext* context, tensorflow::TTypes::Matrix* output_tensor) { @@ -292,6 +329,13 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { max_n_shots = std::max(max_n_shots, num_samples[i][j]); } } + for (int i = 0; i < projector_sums.size(); i++) { + for (int j = 0; j < projector_sums[i].size(); j++) { + max_psum_length = + std::max(max_psum_length, projector_sums[i][j].terms().size()); + max_n_shots = std::max(max_n_shots, num_samples[i][j + pauli_sums[i].size()]); + } + } random_gen.Init(tensorflow::random::New64(), tensorflow::random::New64()); Status compute_status = Status::OK(); @@ -333,6 +377,8 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { param.normalize_before_mea_gates = true; std::vector unused_stats; // Track op-wise stats. + CHECK_EQ(num_samples[i].size(), + pauli_sums[i].size() + projector_sums[i].size()); std::vector run_samples(num_samples[i].size(), 0); std::vector rolling_sums(num_samples[i].size(), 0.0); @@ -357,6 +403,20 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { rolling_sums[j] += static_cast(exp_v); run_samples[j]++; } + for (int j = 0; j < projector_sums[i].size(); j++) { + int p_reps = (num_samples[i][j + pauli_sums[i].size()] + num_threads - 1) / num_threads; + if (run_samples[j + pauli_sums[i].size()] >= p_reps + rep_offset) { + continue; + } + float exp_v = 0.0; + NESTED_FN_STATUS_SYNC( + compute_status, + ComputeSampledExpectationQsim(projector_sums[i][j], sim, ss, sv, + scratch, 1, rand_source, &exp_v), + c_lock); + rolling_sums[j + pauli_sums[i].size()] += static_cast(exp_v); + run_samples[j + pauli_sums[i].size()]++; + } // Check if we have run enough trajectories for all ops. bool break_loop = true; @@ -400,6 +460,7 @@ REGISTER_OP("TfqNoisySampledExpectation") .Input("symbol_names: string") .Input("symbol_values: float") .Input("pauli_sums: string") + .Input("projector_sums: string") .Input("num_samples: int32") .Output("expectations: float") .SetShapeFn([](tensorflow::shape_inference::InferenceContext* c) { @@ -415,8 +476,11 @@ REGISTER_OP("TfqNoisySampledExpectation") tensorflow::shape_inference::ShapeHandle pauli_sums_shape; TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 2, &pauli_sums_shape)); + tensorflow::shape_inference::ShapeHandle projector_sums_shape; + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 2, &projector_sums_shape)); + tensorflow::shape_inference::ShapeHandle num_samples_shape; - TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 2, &num_samples_shape)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 2, &num_samples_shape)); tensorflow::shape_inference::DimensionHandle output_rows = c->Dim(programs_shape, 0); diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.cc b/tensorflow_quantum/core/src/circuit_parser_qsim.cc index 352f1f5f8..1284dc339 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.cc +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.cc @@ -1025,4 +1025,51 @@ Status QsimZBasisCircuitFromPauliTerm( circuit, fused_circuit); } +Status QsimZBasisCircuitFromProjectorTerm( + const ProjectorTerm& term, const int num_qubits, QsimCircuit* circuit, + std::vector>* fused_circuit) { + Program measurement_program; + SymbolMap empty_map; + measurement_program.mutable_circuit()->set_scheduling_strategy( + tfq::proto::Circuit::MOMENT_BY_MOMENT); + Moment* term_moment = measurement_program.mutable_circuit()->add_moments(); + // for (const tfq::proto::ProjectorDictEntry& entry : term.projector_dict()) { + // Operation* new_op = term_moment->add_operations(); + + // // create corresponding eigen gate op. + // new_op->add_qubits()->set_id(entry.qubit_id()); + // new_op->mutable_gate()->set_id("MG1"); + // auto& mutable_args = *new_op->mutable_args(); + // mutable_args["x00"].mutable_arg_value()->set_float_value( + // entry.basis_state() ? 0.0 : 1.0); + // mutable_args["y00"].mutable_arg_value()->set_float_value(0.0); + // mutable_args["x01"].mutable_arg_value()->set_float_value(0.0); + // mutable_args["y01"].mutable_arg_value()->set_float_value(0.0); + // mutable_args["x10"].mutable_arg_value()->set_float_value(0.0); + // mutable_args["y10"].mutable_arg_value()->set_float_value(0.0); + // mutable_args["x11"].mutable_arg_value()->set_float_value( + // entry.basis_state() ? 1.0 : 0.0); + // mutable_args["y11"].mutable_arg_value()->set_float_value(0.0); + // } + for (const tfq::proto::ProjectorDictEntry& entry : term.projector_dict()) { + if (!entry.basis_state()) { + continue; + } + + Operation* new_op = term_moment->add_operations(); + + new_op->add_qubits()->set_id(entry.qubit_id()); + new_op->mutable_gate()->set_id("XP"); + auto& mutable_args = *new_op->mutable_args(); + mutable_args["exponent"].mutable_arg_value()->set_float_value(0.0); + mutable_args["global_shift"].mutable_arg_value()->set_float_value(0.0); + mutable_args["exponent_scalar"].mutable_arg_value()->set_float_value(1.0); + mutable_args["control_values"].mutable_arg_value()->set_string_value(""); + mutable_args["control_qubits"].mutable_arg_value()->set_string_value(""); + } + + return QsimCircuitFromProgram(measurement_program, empty_map, num_qubits, + circuit, fused_circuit); +} + } // namespace tfq diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.h b/tensorflow_quantum/core/src/circuit_parser_qsim.h index 66fc30047..6f920baa9 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.h +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.h @@ -109,6 +109,14 @@ tensorflow::Status QsimZBasisCircuitFromPauliTerm( qsim::Circuit>* circuit, std::vector>>* fused_circuit); +// parse a serialized projectorTerm from a larger cirq.Projectorsum proto +// into a qsim Circuit and fused circuit that represents the transformation +// to the z basis. +tensorflow::Status QsimZBasisCircuitFromProjectorTerm( + const tfq::proto::ProjectorTerm& term, const int num_qubits, + qsim::Circuit>* circuit, + std::vector>>* fused_circuit); + } // namespace tfq #endif // TFQ_CORE_SRC_CIRCUIT_PARSER_QSIM_H_ diff --git a/tensorflow_quantum/core/src/util_qsim.h b/tensorflow_quantum/core/src/util_qsim.h index 92d39d005..f27f170ad 100644 --- a/tensorflow_quantum/core/src/util_qsim.h +++ b/tensorflow_quantum/core/src/util_qsim.h @@ -301,6 +301,88 @@ tensorflow::Status ComputeSampledExpectationQsim( return status; } +// bad style standards here that we are forced to follow from qsim. +// computes the expectation value using +// scratch to save on memory. Implementation does this: +// 1. Copy state onto scratch +// 2. Convert scratch to Z basis +// 3. Compute < state | scratch > via sampling. +// 4. Sum and repeat. +// scratch is required to have memory initialized, but does not require +// values in memory to be set. +template +tensorflow::Status ComputeSampledExpectationQsim( + const tfq::proto::ProjectorSum& projector_sum, const SimT& sim, const StateSpaceT& ss, + StateT& state, StateT& scratch, const int num_samples, + tensorflow::random::SimplePhilox& random_source, float* expectation_value) { + std::uniform_int_distribution<> distrib(1, 1 << 30); + + if (num_samples == 0) { + return tensorflow::Status::OK(); + } + // apply the gates of the pauliterms to a copy of the state vector + // and add up expectation value term by term. + tensorflow::Status status = tensorflow::Status::OK(); + for (const tfq::proto::ProjectorTerm& term : projector_sum.terms()) { + // catch identity terms + if (term.projector_dict_size() == 0) { + *expectation_value += term.coefficient_real(); + // TODO(tonybruguier): error somewhere if identities have any imaginary part + continue; + } + + // Transform state into the measurement basis and sample it + QsimCircuit main_circuit; + std::vector> fused_circuit; + + status = QsimZBasisCircuitFromProjectorTerm(term, state.num_qubits(), + &main_circuit, &fused_circuit); + if (!status.ok()) { + return status; + } + // copy from src to scratch. + ss.Copy(state, scratch); + for (const qsim::GateFused& fused_gate : fused_circuit) { + qsim::ApplyFusedGate(sim, fused_gate, scratch); + } + + if (!status.ok()) { + return status; + } + std::vector state_samples = + ss.Sample(scratch, num_samples, random_source.Rand32()); + + // Find qubits on which to measure parity + std::vector parity_bits; + for (const tfq::proto::ProjectorDictEntry& entry : term.projector_dict()) { + unsigned int location; + // GridQubit id should be parsed down to integer at this upstream + // so it is safe to just use atoi. + (void)absl::SimpleAtoi(entry.qubit_id(), &location); + // Parity functions use little-endian indexing + parity_bits.push_back(state.num_qubits() - location - 1); + } + + // Compute the BitMask. + uint64_t mask = 0; + for (const unsigned int parity_bit : parity_bits) { + mask |= uint64_t(1) << uint64_t(parity_bit); + } + + // Compute the running parity. + int parity_total(0); + int count = 0; + for (const uint64_t state_sample : state_samples) { + count = std::bitset<64>(state_sample & mask).count() & 1; + parity_total += count ? -1 : 1; + } + *expectation_value += static_cast(parity_total) * + term.coefficient_real() / + static_cast(num_samples); + } + return status; +} + // Assumes p_sums.size() == op_coeffs.size() // state stores |psi>. scratch has been created, but does not // require initialization. dest has been created, but does not require From 3571f163bd2e8e9a5fda467cb383c39a85f4aa5a Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Sun, 28 Nov 2021 05:05:27 +0000 Subject: [PATCH 11/20] Intermediate --- .../noisy_sampled_expectation_op_test.py | 2 +- .../core/src/circuit_parser_qsim.cc | 48 ++++++++----------- tensorflow_quantum/core/src/util_qsim.h | 4 +- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py index 6281bc18e..2d89a75da 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py @@ -246,8 +246,8 @@ def test_noisy_expectation_inputs(self): # projector_sums tensor has the wrong type. noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, - util.convert_to_tensor([[x] for x in pauli_sums]), symbol_values_array, [[1.0]] * batch_size, + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) with self.assertRaisesRegex(TypeError, 'missing'): diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.cc b/tensorflow_quantum/core/src/circuit_parser_qsim.cc index 1284dc339..7dcdc33d7 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.cc +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.cc @@ -1032,38 +1032,32 @@ Status QsimZBasisCircuitFromProjectorTerm( SymbolMap empty_map; measurement_program.mutable_circuit()->set_scheduling_strategy( tfq::proto::Circuit::MOMENT_BY_MOMENT); - Moment* term_moment = measurement_program.mutable_circuit()->add_moments(); - // for (const tfq::proto::ProjectorDictEntry& entry : term.projector_dict()) { - // Operation* new_op = term_moment->add_operations(); - - // // create corresponding eigen gate op. - // new_op->add_qubits()->set_id(entry.qubit_id()); - // new_op->mutable_gate()->set_id("MG1"); - // auto& mutable_args = *new_op->mutable_args(); - // mutable_args["x00"].mutable_arg_value()->set_float_value( - // entry.basis_state() ? 0.0 : 1.0); - // mutable_args["y00"].mutable_arg_value()->set_float_value(0.0); - // mutable_args["x01"].mutable_arg_value()->set_float_value(0.0); - // mutable_args["y01"].mutable_arg_value()->set_float_value(0.0); - // mutable_args["x10"].mutable_arg_value()->set_float_value(0.0); - // mutable_args["y10"].mutable_arg_value()->set_float_value(0.0); - // mutable_args["x11"].mutable_arg_value()->set_float_value( - // entry.basis_state() ? 1.0 : 0.0); - // mutable_args["y11"].mutable_arg_value()->set_float_value(0.0); - // } + + std::map> qid_to_counts; for (const tfq::proto::ProjectorDictEntry& entry : term.projector_dict()) { - if (!entry.basis_state()) { - continue; + auto& counts = qid_to_counts[entry.qubit_id()]; + if (entry.basis_state()) { + counts.first += 1; + } else { + counts.second += 1; } + } + Moment* term_moment = measurement_program.mutable_circuit()->add_moments(); + for (const auto& count_pair : qid_to_counts) { Operation* new_op = term_moment->add_operations(); - - new_op->add_qubits()->set_id(entry.qubit_id()); - new_op->mutable_gate()->set_id("XP"); + // create corresponding eigen gate op. + new_op->add_qubits()->set_id(count_pair.first); + new_op->mutable_gate()->set_id("MG1"); auto& mutable_args = *new_op->mutable_args(); - mutable_args["exponent"].mutable_arg_value()->set_float_value(0.0); - mutable_args["global_shift"].mutable_arg_value()->set_float_value(0.0); - mutable_args["exponent_scalar"].mutable_arg_value()->set_float_value(1.0); + mutable_args["x00"].mutable_arg_value()->set_float_value(count_pair.second.first); + mutable_args["y00"].mutable_arg_value()->set_float_value(0.0); + mutable_args["x01"].mutable_arg_value()->set_float_value(count_pair.second.second); + mutable_args["y01"].mutable_arg_value()->set_float_value(0.0); + mutable_args["x10"].mutable_arg_value()->set_float_value(count_pair.second.second); + mutable_args["y10"].mutable_arg_value()->set_float_value(0.0); + mutable_args["x11"].mutable_arg_value()->set_float_value(count_pair.second.first); + mutable_args["y11"].mutable_arg_value()->set_float_value(0.0); mutable_args["control_values"].mutable_arg_value()->set_string_value(""); mutable_args["control_qubits"].mutable_arg_value()->set_string_value(""); } diff --git a/tensorflow_quantum/core/src/util_qsim.h b/tensorflow_quantum/core/src/util_qsim.h index f27f170ad..2ed099f07 100644 --- a/tensorflow_quantum/core/src/util_qsim.h +++ b/tensorflow_quantum/core/src/util_qsim.h @@ -373,8 +373,8 @@ tensorflow::Status ComputeSampledExpectationQsim( int parity_total(0); int count = 0; for (const uint64_t state_sample : state_samples) { - count = std::bitset<64>(state_sample & mask).count() & 1; - parity_total += count ? -1 : 1; + count = std::bitset<64>(state_sample & mask).count() == parity_bits.size(); + parity_total += count ? 1 : 0; } *expectation_value += static_cast(parity_total) * term.coefficient_real() / From db69d3c8bdef70ace2dd6ffe323091b3cacd8626 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Sun, 28 Nov 2021 05:09:10 +0000 Subject: [PATCH 12/20] format --- .../ops/noise/noisy_sampled_expectation_op.py | 7 +- .../noisy_sampled_expectation_op_test.py | 45 ++++++------- .../noise/tfq_noisy_sampled_expectation.cc | 64 +++++++++++-------- .../core/src/circuit_parser_qsim.cc | 12 ++-- tensorflow_quantum/core/src/util_qsim.h | 15 +++-- 5 files changed, 75 insertions(+), 68 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py index 9c4f3cb15..15b0cf08f 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py @@ -20,8 +20,8 @@ NOISY_OP_MODULE = load_module(os.path.join("noise", "_tfq_noise_ops.so")) -def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, projector_sums, - num_samples): +def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, + projector_sums, num_samples): """Estimates (via sampling) expectation values using monte-carlo simulation. Simulate the final state of `programs` given `symbol_values` are placed @@ -97,5 +97,4 @@ def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, proje """ return NOISY_OP_MODULE.tfq_noisy_sampled_expectation( programs, symbol_names, tf.cast(symbol_values, tf.float32), pauli_sums, - projector_sums, - tf.cast(num_samples, dtype=tf.int32)) + projector_sums, tf.cast(num_samples, dtype=tf.int32)) diff --git a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py index 2d89a75da..a68547ce2 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py @@ -89,9 +89,8 @@ def test_noisy_expectation_inputs(self): 'pauli_sums must be rank 2.'): # pauli_sums tensor has too few dimensions. noisy_sampled_expectation_op.sampled_expectation( - util.convert_to_tensor(circuit_batch), - symbol_names, symbol_values_array, - util.convert_to_tensor(list(pauli_sums)), + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, util.convert_to_tensor(list(pauli_sums)), util.convert_to_tensor([[x] for x in projector_sums]), num_samples) @@ -99,11 +98,10 @@ def test_noisy_expectation_inputs(self): 'projector_sums must be rank 2.'): # pauli_sums tensor has too few dimensions. noisy_sampled_expectation_op.sampled_expectation( - util.convert_to_tensor(circuit_batch), - symbol_names, symbol_values_array, + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor(list(projector_sums)), - num_samples) + util.convert_to_tensor(list(projector_sums)), num_samples) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'pauli_sums must be rank 2.'): @@ -180,7 +178,8 @@ def test_noisy_expectation_inputs(self): 'qubits not found in circuit'): # pauli_sums tensor has the right type but invalid values. new_qubits = [cirq.GridQubit(5, 5), cirq.GridQubit(9, 9)] - new_projector_sums = util.random_projector_sums(new_qubits, 2, batch_size) + new_projector_sums = util.random_projector_sums( + new_qubits, 2, batch_size) noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, @@ -193,8 +192,7 @@ def test_noisy_expectation_inputs(self): # pauli_sums tensor has the right type but invalid values 2. noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, - [['junk']] * batch_size, + symbol_values_array, [['junk']] * batch_size, util.convert_to_tensor([[x] for x in new_projector_sums]), num_samples) @@ -205,8 +203,7 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), - [['junk']] * batch_size, - num_samples) + [['junk']] * batch_size, num_samples) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # circuits tensor has the wrong type. @@ -247,8 +244,7 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, [[1.0]] * batch_size, - util.convert_to_tensor([[x] for x in pauli_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) with self.assertRaisesRegex(TypeError, 'missing'): # we are missing an argument. @@ -348,9 +344,8 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): num_samples = [[10000] * 4] * batch_size op_exps = noisy_sampled_expectation_op.sampled_expectation( - util.convert_to_tensor(circuit_batch), - symbol_names, symbol_values_array, - util.convert_to_tensor(batch_pauli_sums), + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, util.convert_to_tensor(batch_pauli_sums), util.convert_to_tensor(batch_projector_sums), num_samples) cirq_exps = batch_util.batch_calculate_expectation( @@ -394,11 +389,9 @@ def test_single_channel(self, channel): num_samples = [[20000] * 4] * batch_size op_exps = noisy_sampled_expectation_op.sampled_expectation( - util.convert_to_tensor(circuit_batch), - symbol_names, symbol_values_array, - util.convert_to_tensor(batch_pauli_sums), - util.convert_to_tensor(batch_projector_sums), - num_samples) + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, util.convert_to_tensor(batch_pauli_sums), + util.convert_to_tensor(batch_projector_sums), num_samples) cirq_exps = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, batch_both, @@ -416,8 +409,8 @@ def test_correctness_empty(self): empty_n_samples = tf.convert_to_tensor([[]], dtype=tf.int32) out = noisy_sampled_expectation_op.sampled_expectation( - empty_circuit, empty_symbols, empty_values, empty_paulis, empty_projectors, - empty_n_samples) + empty_circuit, empty_symbols, empty_values, empty_paulis, + empty_projectors, empty_n_samples) expected = np.array([[]], dtype=np.complex64) self.assertAllClose(out, expected) @@ -432,8 +425,8 @@ def test_correctness_no_circuit(self): empty_n_samples = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.int32) out = noisy_sampled_expectation_op.sampled_expectation( - empty_circuit, empty_symbols, empty_values, empty_paulis, empty_projectors, - empty_n_samples) + empty_circuit, empty_symbols, empty_values, empty_paulis, + empty_projectors, empty_n_samples) self.assertShapeEqual(np.zeros((0, 0)), out) diff --git a/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc b/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc index 7bb1d8a9a..b99246838 100644 --- a/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc +++ b/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc @@ -84,8 +84,9 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { std::vector num_qubits; std::vector> pauli_sums; std::vector> projector_sums; - OP_REQUIRES_OK(context, GetProgramsAndNumQubits(context, &programs, - &num_qubits, &pauli_sums, &projector_sums)); + OP_REQUIRES_OK(context, + GetProgramsAndNumQubits(context, &programs, &num_qubits, + &pauli_sums, &projector_sums)); std::vector maps; OP_REQUIRES_OK(context, GetSymbolMaps(context, &maps)); @@ -157,24 +158,25 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { // alternate parallelization scheme with runtime: // O(n_circuits * max_j(num_samples[i])) with parallelization being // multiple threads per wavefunction. - ComputeLarge(num_qubits, qsim_circuits, pauli_sums, projector_sums, num_samples, context, - &output_tensor); + ComputeLarge(num_qubits, qsim_circuits, pauli_sums, projector_sums, + num_samples, context, &output_tensor); } else { // Runtime: O(n_circuits * max_j(num_samples[i])) with parallelization // being done over number of trials. - ComputeSmall(num_qubits, max_num_qubits, qsim_circuits, pauli_sums, projector_sums, - num_samples, context, &output_tensor); + ComputeSmall(num_qubits, max_num_qubits, qsim_circuits, pauli_sums, + projector_sums, num_samples, context, &output_tensor); } } private: - void ComputeLarge(const std::vector& num_qubits, - const std::vector& ncircuits, - const std::vector>& pauli_sums, - const std::vector>& projector_sums, - const std::vector>& num_samples, - tensorflow::OpKernelContext* context, - tensorflow::TTypes::Matrix* output_tensor) { + void ComputeLarge( + const std::vector& num_qubits, + const std::vector& ncircuits, + const std::vector>& pauli_sums, + const std::vector>& projector_sums, + const std::vector>& num_samples, + tensorflow::OpKernelContext* context, + tensorflow::TTypes::Matrix* output_tensor) { // Instantiate qsim objects. const auto tfq_for = tfq::QsimFor(context); using Simulator = qsim::Simulator; @@ -204,7 +206,8 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { for (int j = 0; j < projector_sums[i].size(); j++) { max_psum_length = std::max(max_psum_length, projector_sums[i][j].terms().size()); - max_n_shots = std::max(max_n_shots, num_samples[i][j + pauli_sums[i].size()]); + max_n_shots = + std::max(max_n_shots, num_samples[i][j + pauli_sums[i].size()]); } } random_gen.Init(tensorflow::random::New64(), tensorflow::random::New64()); @@ -261,13 +264,14 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { run_samples[j]++; } for (int j = 0; j < projector_sums[i].size(); j++) { - if (run_samples[j + pauli_sums[i].size()] >= num_samples[i][j + pauli_sums[i].size()]) { + if (run_samples[j + pauli_sums[i].size()] >= + num_samples[i][j + pauli_sums[i].size()]) { continue; } float exp_v = 0.0; OP_REQUIRES_OK(context, ComputeSampledExpectationQsim( - projector_sums[i][j], sim, ss, sv, scratch, 1, - rand_source, &exp_v)); + projector_sums[i][j], sim, ss, sv, + scratch, 1, rand_source, &exp_v)); rolling_sums[j + pauli_sums[i].size()] += static_cast(exp_v); run_samples[j + pauli_sums[i].size()]++; } @@ -289,14 +293,14 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { } } - void ComputeSmall(const std::vector& num_qubits, - const int max_num_qubits, - const std::vector& ncircuits, - const std::vector>& pauli_sums, - const std::vector>& projector_sums, - const std::vector>& num_samples, - tensorflow::OpKernelContext* context, - tensorflow::TTypes::Matrix* output_tensor) { + void ComputeSmall( + const std::vector& num_qubits, const int max_num_qubits, + const std::vector& ncircuits, + const std::vector>& pauli_sums, + const std::vector>& projector_sums, + const std::vector>& num_samples, + tensorflow::OpKernelContext* context, + tensorflow::TTypes::Matrix* output_tensor) { using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; using QTSimulator = @@ -333,7 +337,8 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { for (int j = 0; j < projector_sums[i].size(); j++) { max_psum_length = std::max(max_psum_length, projector_sums[i][j].terms().size()); - max_n_shots = std::max(max_n_shots, num_samples[i][j + pauli_sums[i].size()]); + max_n_shots = + std::max(max_n_shots, num_samples[i][j + pauli_sums[i].size()]); } } random_gen.Init(tensorflow::random::New64(), tensorflow::random::New64()); @@ -404,7 +409,9 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { run_samples[j]++; } for (int j = 0; j < projector_sums[i].size(); j++) { - int p_reps = (num_samples[i][j + pauli_sums[i].size()] + num_threads - 1) / num_threads; + int p_reps = + (num_samples[i][j + pauli_sums[i].size()] + num_threads - 1) / + num_threads; if (run_samples[j + pauli_sums[i].size()] >= p_reps + rep_offset) { continue; } @@ -414,7 +421,8 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { ComputeSampledExpectationQsim(projector_sums[i][j], sim, ss, sv, scratch, 1, rand_source, &exp_v), c_lock); - rolling_sums[j + pauli_sums[i].size()] += static_cast(exp_v); + rolling_sums[j + pauli_sums[i].size()] += + static_cast(exp_v); run_samples[j + pauli_sums[i].size()]++; } diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.cc b/tensorflow_quantum/core/src/circuit_parser_qsim.cc index 7dcdc33d7..06402b8c6 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.cc +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.cc @@ -1050,13 +1050,17 @@ Status QsimZBasisCircuitFromProjectorTerm( new_op->add_qubits()->set_id(count_pair.first); new_op->mutable_gate()->set_id("MG1"); auto& mutable_args = *new_op->mutable_args(); - mutable_args["x00"].mutable_arg_value()->set_float_value(count_pair.second.first); + mutable_args["x00"].mutable_arg_value()->set_float_value( + count_pair.second.first); mutable_args["y00"].mutable_arg_value()->set_float_value(0.0); - mutable_args["x01"].mutable_arg_value()->set_float_value(count_pair.second.second); + mutable_args["x01"].mutable_arg_value()->set_float_value( + count_pair.second.second); mutable_args["y01"].mutable_arg_value()->set_float_value(0.0); - mutable_args["x10"].mutable_arg_value()->set_float_value(count_pair.second.second); + mutable_args["x10"].mutable_arg_value()->set_float_value( + count_pair.second.second); mutable_args["y10"].mutable_arg_value()->set_float_value(0.0); - mutable_args["x11"].mutable_arg_value()->set_float_value(count_pair.second.first); + mutable_args["x11"].mutable_arg_value()->set_float_value( + count_pair.second.first); mutable_args["y11"].mutable_arg_value()->set_float_value(0.0); mutable_args["control_values"].mutable_arg_value()->set_string_value(""); mutable_args["control_qubits"].mutable_arg_value()->set_string_value(""); diff --git a/tensorflow_quantum/core/src/util_qsim.h b/tensorflow_quantum/core/src/util_qsim.h index 2ed099f07..43274661e 100644 --- a/tensorflow_quantum/core/src/util_qsim.h +++ b/tensorflow_quantum/core/src/util_qsim.h @@ -312,9 +312,10 @@ tensorflow::Status ComputeSampledExpectationQsim( // values in memory to be set. template tensorflow::Status ComputeSampledExpectationQsim( - const tfq::proto::ProjectorSum& projector_sum, const SimT& sim, const StateSpaceT& ss, - StateT& state, StateT& scratch, const int num_samples, - tensorflow::random::SimplePhilox& random_source, float* expectation_value) { + const tfq::proto::ProjectorSum& projector_sum, const SimT& sim, + const StateSpaceT& ss, StateT& state, StateT& scratch, + const int num_samples, tensorflow::random::SimplePhilox& random_source, + float* expectation_value) { std::uniform_int_distribution<> distrib(1, 1 << 30); if (num_samples == 0) { @@ -327,7 +328,8 @@ tensorflow::Status ComputeSampledExpectationQsim( // catch identity terms if (term.projector_dict_size() == 0) { *expectation_value += term.coefficient_real(); - // TODO(tonybruguier): error somewhere if identities have any imaginary part + // TODO(tonybruguier): error somewhere if identities have any imaginary + // part continue; } @@ -336,7 +338,7 @@ tensorflow::Status ComputeSampledExpectationQsim( std::vector> fused_circuit; status = QsimZBasisCircuitFromProjectorTerm(term, state.num_qubits(), - &main_circuit, &fused_circuit); + &main_circuit, &fused_circuit); if (!status.ok()) { return status; } @@ -373,7 +375,8 @@ tensorflow::Status ComputeSampledExpectationQsim( int parity_total(0); int count = 0; for (const uint64_t state_sample : state_samples) { - count = std::bitset<64>(state_sample & mask).count() == parity_bits.size(); + count = + std::bitset<64>(state_sample & mask).count() == parity_bits.size(); parity_total += count ? 1 : 0; } *expectation_value += static_cast(parity_total) * From 61cedc2e9e7c093d2e2324c43c4cdbf9bda16973 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Fri, 3 Dec 2021 06:24:59 +0000 Subject: [PATCH 13/20] Undo some line removals --- tensorflow_quantum/core/serialize/op_deserializer_test.py | 1 + .../python/layers/circuit_executors/sampled_expectation_test.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tensorflow_quantum/core/serialize/op_deserializer_test.py b/tensorflow_quantum/core/serialize/op_deserializer_test.py index c5e56fb3d..f97b2db18 100644 --- a/tensorflow_quantum/core/serialize/op_deserializer_test.py +++ b/tensorflow_quantum/core/serialize/op_deserializer_test.py @@ -325,6 +325,7 @@ def test_from_proto_not_required_ok(self, q): result = deserializer.from_proto(serialized) self.assertEqual(result, GateWithAttribute(0.125)(q)) + def test_from_proto_missing_required_arg(self): """Error raised when required field is missing.""" deserializer = op_deserializer.GateOpDeserializer( diff --git a/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation_test.py b/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation_test.py index c3c07c74c..60714b2e4 100644 --- a/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation_test.py +++ b/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation_test.py @@ -68,6 +68,7 @@ def test_sampled_expectation_symbol_input(self): sampled_expectation.SampledExpectation( differentiator=linear_combination.ForwardDifference()) + def test_sampled_expectation_instantiate_error(self): """Test that SampledExpectation errors with bad inputs.""" From 5af5f53c9a9e467692eec80d6a26d4fd26cdaa13 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Fri, 3 Dec 2021 07:26:51 +0000 Subject: [PATCH 14/20] Make projector optional --- .../core/ops/noise/noisy_expectation_op.py | 14 +- .../ops/noise/noisy_expectation_op_test.py | 136 ++++++++--------- .../ops/noise/noisy_sampled_expectation_op.py | 12 +- .../noisy_sampled_expectation_op_test.py | 138 ++++++++---------- 4 files changed, 141 insertions(+), 159 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py index bae6d4806..6cec86373 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py @@ -20,8 +20,8 @@ NOISY_OP_MODULE = load_module(os.path.join("noise", "_tfq_noise_ops.so")) -def expectation(programs, symbol_names, symbol_values, pauli_sums, - projector_sums, num_samples): +def expectation(programs, symbol_names, symbol_values, pauli_sums, num_samples, + projector_sums=None): """Calculate the analytic expectation values using monte-carlo trajectories. Simulate the final state of `programs` given `symbol_values` are placed @@ -81,9 +81,6 @@ def expectation(programs, symbol_names, symbol_values, pauli_sums, pauli_sums: `tf.Tensor` of strings with shape [batch_size, n_ops] containing the string representation of the operators that will be used on all of the circuits in the expectation calculations. - projector_sums: `tf.Tensor` of strings with shape [batch_size, n_ops] - containing the string representation of the operators that will - be used on all of the circuits in the expectation calculations. num_samples: `tf.Tensor` with `num_samples[i][j]` is equal to the number of times `programs[i]` will be simulated to estimate `pauli_sums[i][j]`. Therefore, `num_samples` must have the same @@ -92,11 +89,18 @@ def expectation(programs, symbol_names, symbol_values, pauli_sums, threads to TensorFlow. For best performance ensure that the quantities in `num_samples` are a multiple of the number of available threads. + projector_sums: `tf.Tensor` of strings with shape [batch_size, n_ops] + containing the string representation of the operators that will + be used on all of the circuits in the expectation calculations. Returns: `tf.Tensor` with shape [batch_size, n_ops] that holds the expectation value for each circuit with each op applied to it (after resolving the corresponding parameters in). """ + # TODO(tonybruguier): Always supply a projector sum to this function and + # remove the special-casing below. + if projector_sums is None: + projector_sums = pauli_sums[:, 0:0] return NOISY_OP_MODULE.tfq_noisy_expectation( programs, symbol_names, tf.cast(symbol_values, tf.float32), pauli_sums, projector_sums, tf.cast(num_samples, dtype=tf.int32)) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py index 942210ca1..5cca30fb1 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op_test.py @@ -51,9 +51,8 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor([circuit_batch]), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_names must be rank 1.'): @@ -61,9 +60,8 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), np.array([symbol_names]), symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_values must be rank 2.'): @@ -71,9 +69,8 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, np.array([symbol_values_array]), - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_values must be rank 2.'): @@ -81,18 +78,17 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array[0], - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'pauli_sums must be rank 2.'): # pauli_sums tensor has too few dimensions. noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, util.convert_to_tensor(list(pauli_sums)), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array, + util.convert_to_tensor(list(pauli_sums)), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'projector_sums must be rank 2.'): @@ -100,8 +96,8 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor(list(projector_sums)), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor(list(projector_sums))) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'pauli_sums must be rank 2.'): @@ -110,8 +106,8 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, [util.convert_to_tensor([[x] for x in pauli_sums])], - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'projector_sums must be rank 2.'): @@ -119,9 +115,8 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - [util.convert_to_tensor([[x] for x in projector_sums])], - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + [util.convert_to_tensor([[x] for x in projector_sums])]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'num_samples must be rank 2'): @@ -130,8 +125,8 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - [num_samples]) + [num_samples], + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'num_samples must be rank 2'): @@ -140,17 +135,16 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples[0]) + num_samples[0], + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Unparseable proto'): # circuit tensor has the right type but invalid values. noisy_expectation_op.expectation( ['junk'] * batch_size, symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Could not find symbol in parameter map'): @@ -158,9 +152,8 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), ['junk'], symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'qubits not found in circuit'): @@ -171,8 +164,8 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in new_pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'qubits not found in circuit'): @@ -183,59 +176,54 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in new_projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in new_projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Unparseable proto'): # pauli_sums tensor has the right type but invalid values 2. noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [['junk']] * batch_size, - [['junk']] * batch_size, num_samples) + symbol_values_array, [['junk']] * batch_size, num_samples, + [['junk']] * batch_size) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # circuits tensor has the wrong type. noisy_expectation_op.expectation( [1.0] * batch_size, symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # symbol_names tensor has the wrong type. noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), [0.1234], symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.UnimplementedError, ''): # symbol_values tensor has the wrong type. noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, [['junk']] * batch_size, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # pauli_sums tensor has the wrong type. noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [[1.0]] * batch_size, - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + symbol_values_array, [[1.0]] * batch_size, num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # pauli_sums tensor has the wrong type. noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - [[1.0]] * batch_size, num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + [[1.0]] * batch_size) with self.assertRaisesRegex(TypeError, 'missing'): # we are missing an argument. @@ -251,8 +239,8 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), [], - num_samples) + util.convert_to_tensor([[x] for x in projector_sums]), + num_samples, []) # pylint: enable=too-many-function-args with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -261,9 +249,8 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor([cirq.Circuit()]), symbol_names, symbol_values_array.astype(np.float64), - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'greater than 0'): @@ -272,8 +259,8 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - [[-1]] * batch_size) + [[-1]] * batch_size, + util.convert_to_tensor([[x] for x in projector_sums])) # pylint: enable=too-many-function-args with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -282,9 +269,8 @@ def test_noisy_expectation_inputs(self): noisy_expectation_op.expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array[:int(batch_size * 0.5)], - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) @parameterized.parameters([ { @@ -335,9 +321,10 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): num_samples = [[10000 if noisy else 3] * 4] * batch_size op_exps = noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, util.convert_to_tensor(batch_pauli_sums), - util.convert_to_tensor(batch_projector_sums), num_samples) + util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array, + util.convert_to_tensor(batch_pauli_sums), num_samples, + util.convert_to_tensor(batch_projector_sums)) cirq_exps = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, batch_both, @@ -380,9 +367,10 @@ def test_single_channel(self, channel): num_samples = [[10000] * 4] * batch_size op_exps = noisy_expectation_op.expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, util.convert_to_tensor(batch_pauli_sums), - util.convert_to_tensor(batch_projector_sums), num_samples) + util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array, + util.convert_to_tensor(batch_pauli_sums), num_samples, + util.convert_to_tensor(batch_projector_sums)) cirq_exps = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, batch_both, @@ -402,8 +390,8 @@ def test_correctness_empty(self): out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, empty_values, empty_paulis, - empty_projector_sums, - empty_n_samples) + empty_n_samples, + empty_projector_sums) expected = np.array([[]], dtype=np.complex64) self.assertAllClose(out, expected) @@ -419,8 +407,8 @@ def test_correctness_no_circuit(self): out = noisy_expectation_op.expectation(empty_circuit, empty_symbols, empty_values, empty_paulis, - empty_projector_sums, - empty_n_samples) + empty_n_samples, + empty_projector_sums) self.assertShapeEqual(np.zeros((0, 0)), out) diff --git a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py index 15b0cf08f..f3b6c0633 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py @@ -21,7 +21,7 @@ def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, - projector_sums, num_samples): + num_samples, projector_sums=None): """Estimates (via sampling) expectation values using monte-carlo simulation. Simulate the final state of `programs` given `symbol_values` are placed @@ -79,9 +79,6 @@ def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, pauli_sums: `tf.Tensor` of strings with shape [batch_size, n_ops] containing the string representation of the operators that will be used on all of the circuits in the expectation calculations. - projector_sums: `tf.Tensor` of strings with shape [batch_size, n_ops] - containing the string representation of the operators that will - be used on all of the circuits in the expectation calculations. num_samples: `tf.Tensor` with `num_samples[i][j]` is equal to the number of times `programs[i]` will be simulated to estimate `pauli_sums[i][j]`. Therefore, `num_samples` must have the same @@ -90,11 +87,18 @@ def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, threads to TensorFlow. For best performance ensure that the quantities in `num_samples` are a multiple of the number of available threads. + projector_sums: `tf.Tensor` of strings with shape [batch_size, n_ops] + containing the string representation of the operators that will + be used on all of the circuits in the expectation calculations. Returns: `tf.Tensor` with shape [batch_size, n_ops] that holds the expectation value for each circuit with each op applied to it (after resolving the corresponding parameters in). """ + # TODO(tonybruguier): Always supply a projector sum to this function and + # remove the special-casing below. + if projector_sums is None: + projector_sums = pauli_sums[:, 0:0] return NOISY_OP_MODULE.tfq_noisy_sampled_expectation( programs, symbol_names, tf.cast(symbol_values, tf.float32), pauli_sums, projector_sums, tf.cast(num_samples, dtype=tf.int32)) diff --git a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py index a68547ce2..8d1df5da9 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op_test.py @@ -51,9 +51,8 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor([circuit_batch]), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_names must be rank 1.'): @@ -61,9 +60,8 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), np.array([symbol_names]), symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_values must be rank 2.'): @@ -71,9 +69,8 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, np.array([symbol_values_array]), - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'symbol_values must be rank 2.'): @@ -81,18 +78,17 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array[0], - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'pauli_sums must be rank 2.'): # pauli_sums tensor has too few dimensions. noisy_sampled_expectation_op.sampled_expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, util.convert_to_tensor(list(pauli_sums)), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array, + util.convert_to_tensor(list(pauli_sums)), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'projector_sums must be rank 2.'): @@ -100,8 +96,8 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor(list(projector_sums)), num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor(list(projector_sums))) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'pauli_sums must be rank 2.'): @@ -110,8 +106,8 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, [util.convert_to_tensor([[x] for x in pauli_sums])], - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'projector_sums must be rank 2.'): @@ -119,9 +115,8 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - [util.convert_to_tensor([[x] for x in projector_sums])], - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + [util.convert_to_tensor([[x] for x in projector_sums])]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'num_samples must be rank 2'): @@ -130,8 +125,8 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - [num_samples]) + [num_samples], + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'num_samples must be rank 2'): @@ -140,17 +135,16 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples[0]) + num_samples[0], + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Unparseable proto'): # circuit tensor has the right type but invalid values. noisy_sampled_expectation_op.sampled_expectation( ['junk'] * batch_size, symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Could not find symbol in parameter map'): @@ -158,9 +152,8 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), ['junk'], symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'qubits not found in circuit'): @@ -171,8 +164,8 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in new_pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'qubits not found in circuit'): @@ -183,18 +176,16 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in new_projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in new_projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Unparseable proto'): # pauli_sums tensor has the right type but invalid values 2. noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [['junk']] * batch_size, - util.convert_to_tensor([[x] for x in new_projector_sums]), - num_samples) + symbol_values_array, [['junk']] * batch_size, num_samples, + util.convert_to_tensor([[x] for x in new_projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Unparseable proto'): @@ -202,49 +193,45 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - [['junk']] * batch_size, num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + [['junk']] * batch_size) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # circuits tensor has the wrong type. noisy_sampled_expectation_op.sampled_expectation( [1.0] * batch_size, symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # symbol_names tensor has the wrong type. noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), [0.1234], symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.UnimplementedError, ''): # symbol_values tensor has the wrong type. noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, [['junk']] * batch_size, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # pauli_sums tensor has the wrong type. noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [[1.0]] * batch_size, - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + symbol_values_array, [[1.0]] * batch_size, num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # projector_sums tensor has the wrong type. noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [[1.0]] * batch_size, - util.convert_to_tensor([[x] for x in pauli_sums]), num_samples) + symbol_values_array, [[1.0]] * batch_size, num_samples, + util.convert_to_tensor([[x] for x in pauli_sums])) with self.assertRaisesRegex(TypeError, 'missing'): # we are missing an argument. @@ -259,9 +246,8 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), [], - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums]), []) # pylint: enable=too-many-function-args with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -270,9 +256,8 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor([cirq.Circuit()]), symbol_names, symbol_values_array.astype(np.float64), - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'greater than 0'): @@ -281,8 +266,8 @@ def test_noisy_expectation_inputs(self): util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - [[-1]] * batch_size) + [[-1]] * batch_size, + util.convert_to_tensor([[x] for x in projector_sums])) # pylint: enable=too-many-function-args with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -291,9 +276,8 @@ def test_noisy_expectation_inputs(self): noisy_sampled_expectation_op.sampled_expectation( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array[:int(batch_size * 0.5)], - util.convert_to_tensor([[x] for x in pauli_sums]), - util.convert_to_tensor([[x] for x in projector_sums]), - num_samples) + util.convert_to_tensor([[x] for x in pauli_sums]), num_samples, + util.convert_to_tensor([[x] for x in projector_sums])) @parameterized.parameters([ { @@ -344,9 +328,10 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): num_samples = [[10000] * 4] * batch_size op_exps = noisy_sampled_expectation_op.sampled_expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, util.convert_to_tensor(batch_pauli_sums), - util.convert_to_tensor(batch_projector_sums), num_samples) + util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array, + util.convert_to_tensor(batch_pauli_sums), num_samples, + util.convert_to_tensor(batch_projector_sums)) cirq_exps = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, batch_both, @@ -389,9 +374,10 @@ def test_single_channel(self, channel): num_samples = [[20000] * 4] * batch_size op_exps = noisy_sampled_expectation_op.sampled_expectation( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, util.convert_to_tensor(batch_pauli_sums), - util.convert_to_tensor(batch_projector_sums), num_samples) + util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array, + util.convert_to_tensor(batch_pauli_sums), num_samples, + util.convert_to_tensor(batch_projector_sums)) cirq_exps = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, batch_both, @@ -410,7 +396,7 @@ def test_correctness_empty(self): out = noisy_sampled_expectation_op.sampled_expectation( empty_circuit, empty_symbols, empty_values, empty_paulis, - empty_projectors, empty_n_samples) + empty_n_samples, empty_projectors) expected = np.array([[]], dtype=np.complex64) self.assertAllClose(out, expected) @@ -426,7 +412,7 @@ def test_correctness_no_circuit(self): out = noisy_sampled_expectation_op.sampled_expectation( empty_circuit, empty_symbols, empty_values, empty_paulis, - empty_projectors, empty_n_samples) + empty_n_samples, empty_projectors) self.assertShapeEqual(np.zeros((0, 0)), out) From 2f458ce339e992063c56d5df4dbe8da362d36703 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Fri, 3 Dec 2021 13:08:00 +0000 Subject: [PATCH 15/20] nit --- tensorflow_quantum/core/ops/noise/noisy_expectation_op.py | 6 +++++- .../core/ops/noise/noisy_sampled_expectation_op.py | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py index 6cec86373..5407b0f14 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py @@ -20,7 +20,11 @@ NOISY_OP_MODULE = load_module(os.path.join("noise", "_tfq_noise_ops.so")) -def expectation(programs, symbol_names, symbol_values, pauli_sums, num_samples, +def expectation(programs, + symbol_names, + symbol_values, + pauli_sums, + num_samples, projector_sums=None): """Calculate the analytic expectation values using monte-carlo trajectories. diff --git a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py index f3b6c0633..249d6345e 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py @@ -20,8 +20,12 @@ NOISY_OP_MODULE = load_module(os.path.join("noise", "_tfq_noise_ops.so")) -def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, - num_samples, projector_sums=None): +def sampled_expectation(programs, + symbol_names, + symbol_values, + pauli_sums, + num_samples, + projector_sums=None): """Estimates (via sampling) expectation values using monte-carlo simulation. Simulate the final state of `programs` given `symbol_values` are placed From 7ca8ff06b31f690d0c9dfcb1a3caa0998dc82c2c Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Fri, 3 Dec 2021 13:08:00 +0000 Subject: [PATCH 16/20] nit --- tensorflow_quantum/core/ops/noise/noisy_expectation_op.py | 6 +++++- .../core/ops/noise/noisy_sampled_expectation_op.py | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py index 6cec86373..5407b0f14 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py @@ -20,7 +20,11 @@ NOISY_OP_MODULE = load_module(os.path.join("noise", "_tfq_noise_ops.so")) -def expectation(programs, symbol_names, symbol_values, pauli_sums, num_samples, +def expectation(programs, + symbol_names, + symbol_values, + pauli_sums, + num_samples, projector_sums=None): """Calculate the analytic expectation values using monte-carlo trajectories. diff --git a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py index f3b6c0633..249d6345e 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py @@ -20,8 +20,12 @@ NOISY_OP_MODULE = load_module(os.path.join("noise", "_tfq_noise_ops.so")) -def sampled_expectation(programs, symbol_names, symbol_values, pauli_sums, - num_samples, projector_sums=None): +def sampled_expectation(programs, + symbol_names, + symbol_values, + pauli_sums, + num_samples, + projector_sums=None): """Estimates (via sampling) expectation values using monte-carlo simulation. Simulate the final state of `programs` given `symbol_values` are placed From d673912ac6cd3f199852c4b65abf883ea712c225 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Sat, 4 Dec 2021 01:15:19 +0000 Subject: [PATCH 17/20] longer timeouts for tests --- tensorflow_quantum/core/ops/noise/BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow_quantum/core/ops/noise/BUILD b/tensorflow_quantum/core/ops/noise/BUILD index 073f4b6e4..4bc5d08be 100644 --- a/tensorflow_quantum/core/ops/noise/BUILD +++ b/tensorflow_quantum/core/ops/noise/BUILD @@ -83,6 +83,7 @@ py_library( py_test( name = "noisy_expectation_op_test", + timeout = "long", srcs = ["noisy_expectation_op_test.py"], python_version = "PY3", deps = [ @@ -103,6 +104,7 @@ py_library( py_test( name = "noisy_sampled_expectation_op_test", + timeout = "long", srcs = ["noisy_sampled_expectation_op_test.py"], python_version = "PY3", deps = [ From 68120a178372d2258923432bf56c4e037edc3967 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Sat, 4 Dec 2021 07:38:11 +0000 Subject: [PATCH 18/20] special casing --- tensorflow_quantum/core/ops/noise/noisy_expectation_op.py | 5 ++++- .../core/ops/noise/noisy_sampled_expectation_op.py | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py index 5407b0f14..b7e85eb8d 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_expectation_op.py @@ -104,7 +104,10 @@ def expectation(programs, # TODO(tonybruguier): Always supply a projector sum to this function and # remove the special-casing below. if projector_sums is None: - projector_sums = pauli_sums[:, 0:0] + if len(pauli_sums.shape) == 1: + projector_sums = pauli_sums[0:0] + else: + projector_sums = pauli_sums[:, 0:0] return NOISY_OP_MODULE.tfq_noisy_expectation( programs, symbol_names, tf.cast(symbol_values, tf.float32), pauli_sums, projector_sums, tf.cast(num_samples, dtype=tf.int32)) diff --git a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py index 249d6345e..94bd16660 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py +++ b/tensorflow_quantum/core/ops/noise/noisy_sampled_expectation_op.py @@ -102,7 +102,11 @@ def sampled_expectation(programs, # TODO(tonybruguier): Always supply a projector sum to this function and # remove the special-casing below. if projector_sums is None: - projector_sums = pauli_sums[:, 0:0] + if len(pauli_sums.shape) == 1: + projector_sums = pauli_sums[0:0] + else: + projector_sums = pauli_sums[:, 0:0] + print(f'TONYBOOM projector_sums.shape={projector_sums.shape}') return NOISY_OP_MODULE.tfq_noisy_sampled_expectation( programs, symbol_names, tf.cast(symbol_values, tf.float32), pauli_sums, projector_sums, tf.cast(num_samples, dtype=tf.int32)) From 4254f3a17c00c7e7b9df6f372e00f3136c643506 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Thu, 6 Jan 2022 05:44:29 +0000 Subject: [PATCH 19/20] nits --- .../core/ops/math_ops/tfq_simulate_1d_expectation.cc | 2 +- tensorflow_quantum/core/ops/parse_context.cc | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_expectation.cc b/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_expectation.cc index 03aecae7c..6fcb9d5d9 100644 --- a/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_expectation.cc +++ b/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_expectation.cc @@ -85,7 +85,7 @@ class TfqSimulateMPS1DExpectationOp : public tensorflow::OpKernel { // is resolved. OP_REQUIRES_OK(context, GetProgramsAndNumQubits(context, &programs, &num_qubits, - &pauli_sums, true)); + &pauli_sums, nullptr, true)); std::vector maps; OP_REQUIRES_OK(context, GetSymbolMaps(context, &maps)); diff --git a/tensorflow_quantum/core/ops/parse_context.cc b/tensorflow_quantum/core/ops/parse_context.cc index 597721b2d..4f3c30be4 100644 --- a/tensorflow_quantum/core/ops/parse_context.cc +++ b/tensorflow_quantum/core/ops/parse_context.cc @@ -201,11 +201,13 @@ Status GetProgramsAndNumQubits( if (p_sums || proj_sums) { auto iter_p_sums = p_sums ? &(p_sums->at(i)) : nullptr; auto iter_proj_sums = proj_sums ? &(proj_sums->at(i)) : nullptr; - OP_REQUIRES_OK(context, ResolveQubitIds(&program, &this_num_qubits, - iter_p_sums, iter_proj_sums, swap_endianness)); + OP_REQUIRES_OK(context, + ResolveQubitIds(&program, &this_num_qubits, iter_p_sums, + iter_proj_sums, swap_endianness)); } else { - OP_REQUIRES_OK(context, ResolveQubitIds(&program, &this_num_qubits, - nullptr, swap_endianness)); + OP_REQUIRES_OK(context, + ResolveQubitIds(&program, &this_num_qubits, nullptr, + nullptr, swap_endianness)); } (*num_qubits)[i] = this_num_qubits; } From adb22a45c47cd48a2449f629b35726776a2249c9 Mon Sep 17 00:00:00 2001 From: Tony Bruguier Date: Sat, 22 Jan 2022 21:14:07 +0000 Subject: [PATCH 20/20] Attempt to fix build --- .../core/ops/math_ops/tfq_simulate_1d_sampled_expectation.cc | 2 +- tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_samples.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_sampled_expectation.cc b/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_sampled_expectation.cc index 750531f16..2604615b1 100644 --- a/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_sampled_expectation.cc +++ b/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_sampled_expectation.cc @@ -81,7 +81,7 @@ class TfqSimulateMPS1DSampledExpectationOp : public tensorflow::OpKernel { std::vector> pauli_sums; OP_REQUIRES_OK(context, GetProgramsAndNumQubits(context, &programs, &num_qubits, - &pauli_sums, true)); + &pauli_sums, nullptr, true)); std::vector maps; OP_REQUIRES_OK(context, GetSymbolMaps(context, &maps)); diff --git a/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_samples.cc b/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_samples.cc index 495e5f8f2..962615a00 100644 --- a/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_samples.cc +++ b/tensorflow_quantum/core/ops/math_ops/tfq_simulate_1d_samples.cc @@ -66,7 +66,7 @@ class TfqSimulateMPS1DSamplesOp : public tensorflow::OpKernel { std::vector num_qubits; OP_REQUIRES_OK(context, GetProgramsAndNumQubits(context, &programs, &num_qubits, - nullptr, true)); + nullptr, nullptr, true)); // Parse symbol maps for parameter resolution in the circuits. std::vector maps;