From 02c2d55f695e88fd70c21e18c062318bca3fcd98 Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Wed, 24 Mar 2021 21:09:22 -0700 Subject: [PATCH 1/3] Adds tfq.noise.samples op. --- release/BUILD | 1 + scripts/import_test.py | 1 + tensorflow_quantum/core/ops/noise/BUILD | 23 ++ tensorflow_quantum/core/ops/noise/__init__.py | 1 + .../core/ops/noise/noisy_samples_op.py | 71 ++++ .../core/ops/noise/noisy_samples_op_test.py | 301 +++++++++++++++ .../core/ops/noise/tfq_noisy_expectation.cc | 2 +- .../core/ops/noise/tfq_noisy_samples.cc | 348 ++++++++++++++++++ .../core/src/circuit_parser_qsim.cc | 12 + .../core/src/circuit_parser_qsim.h | 6 +- .../core/src/circuit_parser_qsim_test.cc | 30 +- tensorflow_quantum/core/src/util_qsim.h | 39 ++ tensorflow_quantum/core/src/util_qsim_test.cc | 31 ++ tensorflow_quantum/python/util.py | 3 +- 14 files changed, 853 insertions(+), 16 deletions(-) create mode 100644 tensorflow_quantum/core/ops/noise/noisy_samples_op.py create mode 100644 tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py create mode 100644 tensorflow_quantum/core/ops/noise/tfq_noisy_samples.cc diff --git a/release/BUILD b/release/BUILD index 55c938713..6892e1a21 100644 --- a/release/BUILD +++ b/release/BUILD @@ -40,6 +40,7 @@ sh_binary( "//tensorflow_quantum/core/ops:tfq_utility_ops_py", "//tensorflow_quantum/core/ops:tfq_simulate_ops_py", "//tensorflow_quantum/core/ops/math_ops:inner_product_op_py", + "//tensorflow_quantum/core/ops/noise:noisy_samples_op_py", "//tensorflow_quantum/core/ops/noise:noisy_expectation_op_py", "//tensorflow_quantum/core/serialize:serializer", "//tensorflow_quantum/datasets:cluster_state", diff --git a/scripts/import_test.py b/scripts/import_test.py index b5bd1888c..a92a09795 100644 --- a/scripts/import_test.py +++ b/scripts/import_test.py @@ -39,6 +39,7 @@ def test_imports(): # Noisy simulation ops. _ = tfq.noise.expectation + _ = tfq.noise.samples # Util functions. _ = tfq.convert_to_tensor diff --git a/tensorflow_quantum/core/ops/noise/BUILD b/tensorflow_quantum/core/ops/noise/BUILD index 9f31bd666..a74792259 100644 --- a/tensorflow_quantum/core/ops/noise/BUILD +++ b/tensorflow_quantum/core/ops/noise/BUILD @@ -14,6 +14,7 @@ cc_binary( name = "_tfq_noise_ops.so", srcs = [ "tfq_noisy_expectation.cc", + "tfq_noisy_samples.cc" ], copts = select({ ":windows": [ @@ -83,3 +84,25 @@ py_test( "//tensorflow_quantum/python:util", ], ) + +py_library( + name = "noisy_samples_op_py", + srcs = ["noisy_samples_op.py"], + data = [":_tfq_noise_ops.so"], + deps = [ + "//tensorflow_quantum/core/ops:load_module", + "//tensorflow_quantum/core/ops:tfq_utility_ops_py", + ], +) + +py_test( + name = "noisy_samples_op_test", + srcs = ["noisy_samples_op_test.py"], + python_version = "PY3", + deps = [ + ":noisy_samples_op_py", + "//tensorflow_quantum/core/ops:batch_util", + "//tensorflow_quantum/python:util", + ], +) + diff --git a/tensorflow_quantum/core/ops/noise/__init__.py b/tensorflow_quantum/core/ops/noise/__init__.py index 23243dd8d..8bbe7fa27 100644 --- a/tensorflow_quantum/core/ops/noise/__init__.py +++ b/tensorflow_quantum/core/ops/noise/__init__.py @@ -15,3 +15,4 @@ """Module for tfq.core.ops.noise.*""" from tensorflow_quantum.core.ops.noise.noisy_expectation_op import expectation +from tensorflow_quantum.core.ops.noise.noisy_samples_op import samples diff --git a/tensorflow_quantum/core/ops/noise/noisy_samples_op.py b/tensorflow_quantum/core/ops/noise/noisy_samples_op.py new file mode 100644 index 000000000..4441d0c04 --- /dev/null +++ b/tensorflow_quantum/core/ops/noise/noisy_samples_op.py @@ -0,0 +1,71 @@ +# Copyright 2020 The TensorFlow Quantum Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Module for high performance noisy circuit sampling ops""" +import os +import tensorflow as tf +from tensorflow_quantum.core.ops import tfq_utility_ops +from tensorflow_quantum.core.ops.load_module import load_module + +NOISY_OP_MODULE = load_module(os.path.join("noise", "_tfq_noise_ops.so")) + + +def samples(programs, symbol_names, symbol_values, num_samples): + """Generate samples using the C++ noisy trajectory simulator. + + Simulate the final state of `programs` given `symbol_values` are placed + inside of the symbols with the name in `symbol_names` in each circuit. + Channels in this simulation will be "tossed" to a certain realization + during simulation. After each simulation is a run a single bitstring + will be drawn. These simulations are repeated `num_samples` times. + + + >>> # Sample a noisy circuit with C++. + >>> qubit = cirq.GridQubit(0, 0) + >>> my_symbol = sympy.Symbol('alpha') + >>> my_circuit_tensor = tfq.convert_to_tensor([ + ... cirq.Circuit( + ... cirq.X(qubit) ** my_symbol, + ... cirq.depolarize(0.01)(qubit) + ... ) + ... ]) + >>> my_values = np.array([[0.123]]) + >>> my_num_samples = np.array([100]) + >>> # This op can now be run with: + >>> output = tfq.noise.samples( + ... my_circuit_tensor, ['alpha'], my_values, my_num_samples) + >>> output + + + + Args: + programs: `tf.Tensor` of strings with shape [batch_size] containing + the string representations of the circuits to be executed. + symbol_names: `tf.Tensor` of strings with shape [n_params], which + is used to specify the order in which the values in + `symbol_values` should be placed inside of the circuits in + `programs`. + symbol_values: `tf.Tensor` of real numbers with shape + [batch_size, n_params] specifying parameter values to resolve + into the circuits specified by programs, following the ordering + dictated by `symbol_names`. + num_samples: `tf.Tensor` with one element indicating the number of + samples to draw for all circuits in the batch. + Returns: + A `tf.Tensor` containing the samples taken from each circuit in + `programs`. + """ + padded_samples = NOISY_OP_MODULE.tfq_noisy_samples( + programs, symbol_names, tf.cast(symbol_values, tf.float32), num_samples) + return tfq_utility_ops.padded_to_ragged(padded_samples) \ No newline at end of file diff --git a/tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py new file mode 100644 index 000000000..0495dc768 --- /dev/null +++ b/tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py @@ -0,0 +1,301 @@ +# Copyright 2020 The TensorFlow Quantum Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests that specifically target noisy sampling.""" +import numpy as np +from scipy import stats +from absl.testing import parameterized +import tensorflow as tf +import cirq + +from tensorflow_quantum.core.ops import batch_util +from tensorflow_quantum.core.ops.noise import noisy_samples_op +from tensorflow_quantum.python import util + + +class NoisySamplingTest(tf.test.TestCase, parameterized.TestCase): + """Tests tfq.noise.expectation.""" + + def _compute_hists(self, x, n_qubits): + """Compute the batchwise histograms of a sample tensor.""" + x = np.asarray(x) + return [ + np.histogram( + sample.dot(1 << np.arange(sample.shape[-1] - 1, -1, -1)), + range=(0, 2**n_qubits), + bins=2**n_qubits)[0] for sample in x + ] + + def test_simulate_samples_inputs(self): + """Make sure the sample op fails gracefully on bad inputs.""" + n_qubits = 5 + batch_size = 5 + num_samples = 10 + 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) + + symbol_values_array = np.array( + [[resolver[symbol] + for symbol in symbol_names] + for resolver in resolver_batch]) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'rank 1. Got rank 2'): + # programs tensor has the wrong shape. + noisy_samples_op.samples( + util.convert_to_tensor([circuit_batch]), symbol_names, + symbol_values_array, [num_samples]) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'rank 1. Got rank 2'): + # symbol_names tensor has the wrong shape. + noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), np.array([symbol_names]), + symbol_values_array, [num_samples]) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'rank 2. Got rank 3'): + # symbol_values tensor has the wrong shape. + noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), symbol_names, + np.array([symbol_values_array]), [num_samples]) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'rank 2. Got rank 1'): + # symbol_values tensor has the wrong shape 2. + noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array[0], [num_samples]) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'rank 1. Got rank 2'): + # num_samples tensor has the wrong shape. + noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, [[num_samples]]) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'Unparseable proto'): + # programs tensor has the right type, but invalid value. + noisy_samples_op.samples(['junk'] * batch_size, + symbol_names, + symbol_values_array, + [num_samples]) + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + 'Could not find symbol in parameter map'): + # symbol_names tensor has the right type, but invalid value. + noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), ['junk'], + symbol_values_array, [num_samples]) + + with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # programs tensor has the wrong type. + noisy_samples_op.samples([1] * batch_size, + symbol_names, + symbol_values_array, + [num_samples]) + + with self.assertRaisesRegex(TypeError, 'Cannot convert'): + # programs tensor has the wrong type. + noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), [1], symbol_values_array, + [num_samples]) + + with self.assertRaisesRegex(tf.errors.UnimplementedError, + 'Cast string to float is not supported'): + # programs tensor has the wrong type. + noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), symbol_names, + [['junk']] * batch_size, [num_samples]) + + with self.assertRaisesRegex(Exception, 'junk'): + # num_samples tensor has the wrong shape. + noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, ['junk']) + + with self.assertRaisesRegex(TypeError, 'missing'): + # too few tensors. + # pylint: disable=no-value-for-parameter + noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array) + # pylint: enable=no-value-for-parameter + + with self.assertRaisesRegex(tf.errors.InvalidArgumentError, + expected_regex='do not match'): + # wrong symbol_values size. + noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array[:int(batch_size * 0.5)], num_samples) + + @parameterized.parameters([ + { + '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. + ]) + def test_simulate_consistency(self, batch_size, n_qubits, noisy): + """Test consistency with batch_util.py simulation.""" + symbol_names = ['alpha', 'beta'] + qubits = cirq.GridQubit.rect(1, n_qubits) + + circuit_batch, resolver_batch = \ + util.random_symbol_circuit_resolver_batch( + qubits, symbol_names, batch_size, include_channels=noisy) + + symbol_values_array = np.array( + [[resolver[symbol] + for symbol in symbol_names] + for resolver in resolver_batch]) + + n_samples = 10000 + op_samples = noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array, [n_samples]).to_list() + + op_hists = self._compute_hists(op_samples, n_qubits) + + cirq_samples = batch_util.batch_sample( + circuit_batch, + resolver_batch, + n_samples, + cirq.DensityMatrixSimulator() if noisy else cirq.Simulator()) + + cirq_hists = self._compute_hists(cirq_samples, n_qubits) + tol = 1.3 if noisy else 1.0 + for a, b in zip(op_hists, cirq_hists): + self.assertLess(stats.entropy(a + 1e-8, b + 1e-8), 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 = 3 + n_qubits = 5 + qubits = cirq.GridQubit.rect(1, 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]) + + n_samples = (2**n_qubits) * 1000 + + op_samples = noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array, [n_samples]).to_list() + op_hists = self._compute_hists(op_samples, n_qubits) + + cirq_samples = batch_util.batch_sample( + circuit_batch, + resolver_batch, + n_samples, cirq.DensityMatrixSimulator()) + cirq_hists = self._compute_hists(cirq_samples, n_qubits) + + for a, b in zip(op_hists, cirq_hists): + self.assertLess(stats.entropy(a + 1e-8, b + 1e-8), 0.15) + + def test_correct_padding(self): + """Test the variable sized circuits are properly padded.""" + symbol_names = [] + batch_size = 2 + n_qubits = 5 + qubits1 = cirq.GridQubit.rect(1, n_qubits) + qubits2 = cirq.GridQubit.rect(1, n_qubits + 1) + + circuit_batch1, resolver_batch = \ + util.random_circuit_resolver_batch( + qubits1, batch_size, include_channels=True) + + circuit_batch2, _ = \ + util.random_circuit_resolver_batch( + qubits2, batch_size, include_channels=True) + + symbol_values_array = np.array( + [[resolver[symbol] + for symbol in symbol_names] + for resolver in resolver_batch] * 2) + + n_samples = 10 + + op_samples = noisy_samples_op.samples( + util.convert_to_tensor(circuit_batch1 + circuit_batch2), + symbol_names, symbol_values_array, [n_samples]).to_list() + a_reps = np.asarray(op_samples[:2]) + b_reps = np.asarray(op_samples[2:]) + print(a_reps) + print(b_reps) + self.assertEqual(a_reps.shape, (2, 10, 5)) + self.assertEqual(b_reps.shape, (2, 10, 6)) + + 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_n_samples = tf.convert_to_tensor([1], dtype=tf.int32) + + out = noisy_samples_op.samples(empty_circuit, empty_symbols, + empty_values, + empty_n_samples) + + expected = np.array([[[]]], dtype=np.int8) + self.assertAllClose(out.to_tensor(), 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_n_samples = tf.convert_to_tensor([1], dtype=tf.int32) + + out = noisy_samples_op.samples(empty_circuit, empty_symbols, + empty_values, + empty_n_samples) + + self.assertShapeEqual(np.zeros((0, 0, 0)), out.to_tensor()) + + +if __name__ == "__main__": + tf.test.main() diff --git a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc index 8c82474c3..588ffefb1 100644 --- a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc +++ b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc @@ -110,7 +110,7 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { auto construct_f = [&](int start, int end) { for (int i = start; i < end; i++) { OP_REQUIRES_OK(context, NoisyQsimCircuitFromProgram( - programs[i], maps[i], num_qubits[i], + programs[i], maps[i], num_qubits[i], false, &qsim_circuits[i])); } }; diff --git a/tensorflow_quantum/core/ops/noise/tfq_noisy_samples.cc b/tensorflow_quantum/core/ops/noise/tfq_noisy_samples.cc new file mode 100644 index 000000000..0990f15cf --- /dev/null +++ b/tensorflow_quantum/core/ops/noise/tfq_noisy_samples.cc @@ -0,0 +1,348 @@ +/* Copyright 2020 The TensorFlow Quantum Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include + +#include + +#include "../qsim/lib/channel.h" +#include "../qsim/lib/channels_cirq.h" +#include "../qsim/lib/circuit.h" +#include "../qsim/lib/circuit_noisy.h" +#include "../qsim/lib/fuser_mqubit.h" +#include "../qsim/lib/gate_appl.h" +#include "../qsim/lib/gates_cirq.h" +#include "../qsim/lib/io.h" +#include "../qsim/lib/qtrajectory.h" +#include "../qsim/lib/seqfor.h" +#include "../qsim/lib/simmux.h" +#include "cirq/google/api/v2/program.pb.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include "tensorflow/core/framework/tensor_shape.h" +#include "tensorflow/core/lib/core/error_codes.pb.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/lib/core/threadpool.h" +#include "tensorflow_quantum/core/ops/parse_context.h" +#include "tensorflow_quantum/core/src/circuit_parser_qsim.h" +#include "tensorflow_quantum/core/src/util_qsim.h" + +namespace tfq { + +using ::cirq::google::api::v2::Program; +using ::tensorflow::Status; + +typedef qsim::Cirq::GateCirq QsimGate; +typedef qsim::Circuit QsimCircuit; +typedef qsim::NoisyCircuit NoisyQsimCircuit; + +class TfqNoisySamplesOp : public tensorflow::OpKernel { + public: + explicit TfqNoisySamplesOp(tensorflow::OpKernelConstruction* context) + : OpKernel(context) {} + + void Compute(tensorflow::OpKernelContext* context) override { + // TODO (mbbrough): add more dimension checks for other inputs here. + DCHECK_EQ(4, context->num_inputs()); + + // Parse to Program Proto and num_qubits. + std::vector programs; + std::vector num_qubits; + OP_REQUIRES_OK(context, + GetProgramsAndNumQubits(context, &programs, &num_qubits)); + + // Parse symbol maps for parameter resolution in the circuits. + std::vector maps; + OP_REQUIRES_OK(context, GetSymbolMaps(context, &maps)); + OP_REQUIRES( + context, maps.size() == programs.size(), + tensorflow::errors::InvalidArgument(absl::StrCat( + "Number of circuits and values do not match. Got ", programs.size(), + " circuits and ", maps.size(), " values."))); + + int num_samples = 0; + OP_REQUIRES_OK(context, GetIndividualSample(context, &num_samples)); + + // Construct qsim circuits. + std::vector qsim_circuits(programs.size(), + NoisyQsimCircuit()); + + Status parse_status = Status::OK(); + auto p_lock = tensorflow::mutex(); + auto construct_f = [&](int start, int end) { + for (int i = start; i < end; i++) { + auto r = NoisyQsimCircuitFromProgram( + programs[i], maps[i], num_qubits[i], true, &qsim_circuits[i]); + if (!r.ok()) { + p_lock.lock(); + parse_status = r; + p_lock.unlock(); + return; + } + } + }; + + const int num_cycles = 1000; + context->device()->tensorflow_cpu_worker_threads()->workers->ParallelFor( + programs.size(), num_cycles, construct_f); + OP_REQUIRES_OK(context, parse_status); + + int max_num_qubits = 0; + for (const int num : num_qubits) { + max_num_qubits = std::max(max_num_qubits, num); + } + + const int output_dim_size = maps.size(); + tensorflow::TensorShape output_shape; + output_shape.AddDim(output_dim_size); + output_shape.AddDim(num_samples); + output_shape.AddDim(max_num_qubits); + + tensorflow::Tensor* output = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, output_shape, &output)); + auto output_tensor = output->tensor(); + + if (num_samples == 0 || output_dim_size == 0 || max_num_qubits == 0) { + return; // bug in qsim dependency we can't control. + } + + // Cross reference with standard google cloud compute instances + // Memory ~= 2 * num_threads * (2 * 64 * 2 ** num_qubits in circuits) + // e2s2 = 2 CPU, 8GB -> Can safely do 25 since Memory = 4GB + // e2s4 = 4 CPU, 16GB -> Can safely do 25 since Memory = 8GB + // ... + if (max_num_qubits >= 26) { + ComputeLarge(num_qubits, max_num_qubits, num_samples, qsim_circuits, + context, &output_tensor); + } else { + ComputeSmall(num_qubits, max_num_qubits, num_samples, qsim_circuits, + context, &output_tensor); + } + } + + private: + void ComputeLarge(const std::vector& num_qubits, + const int max_num_qubits, const int num_samples, + const std::vector& ncircuits, + tensorflow::OpKernelContext* context, + tensorflow::TTypes::Tensor* output_tensor) { + // Instantiate qsim objects. + const auto tfq_for = tfq::QsimFor(context); + using Simulator = qsim::Simulator; + using StateSpace = Simulator::StateSpace; + using QTSimulator = + qsim::QuantumTrajectorySimulator; + + // Begin simulation. + int largest_nq = 1; + Simulator sim = Simulator(tfq_for); + StateSpace ss = StateSpace(tfq_for); + auto sv = ss.Create(largest_nq); + auto scratch = ss.Create(largest_nq); + + // Simulate programs one by one. Parallelizing over state vectors + // we no longer parallelize over circuits. Each time we encounter a + // a larger circuit we will grow the Statevector as nescessary. + for (int i = 0; i < ncircuits.size(); i++) { + int nq = num_qubits[i]; + + if (nq > largest_nq) { + // need to switch to larger statespace. + largest_nq = nq; + sv = ss.Create(largest_nq); + scratch = ss.Create(largest_nq); + } + + QTSimulator::Parameter param; + param.collect_kop_stat = false; + param.collect_mea_stat = true; + param.normalize_before_mea_gates = true; + + // Track op-wise stats. + std::vector gathered_samples; + + for (int j = 0; j < num_samples; j++) { + ss.SetStateZero(sv); + // time since epoch seeds random generator. + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + QTSimulator::RunOnce(param, ncircuits[i], r_seed, ss, sim, scratch, sv, + gathered_samples); + uint64_t q_ind = 0; + uint64_t mask = 1; + bool val = 0; + while (q_ind < nq) { + val = gathered_samples[0] & mask; + (*output_tensor)( + i, j, static_cast(max_num_qubits - q_ind - 1)) = val; + q_ind++; + mask <<= 1; + } + while (q_ind < max_num_qubits) { + (*output_tensor)( + i, j, static_cast(max_num_qubits - q_ind - 1)) = -2; + q_ind++; + } + } + } + } + + void ComputeSmall(const std::vector& num_qubits, + const int max_num_qubits, const int num_samples, + const std::vector& ncircuits, + tensorflow::OpKernelContext* context, + tensorflow::TTypes::Tensor* output_tensor) { + using Simulator = qsim::Simulator; + using StateSpace = Simulator::StateSpace; + using QTSimulator = + qsim::QuantumTrajectorySimulator; + + const int output_dim_batch_size = output_tensor->dimension(0); + const int num_threads = context->device() + ->tensorflow_cpu_worker_threads() + ->workers->NumThreads(); + + // [num_threads, batch_size]. + std::vector> rep_offsets( + num_threads, std::vector(output_dim_batch_size, 0)); + BalanceTrajectory(num_samples, num_threads, &rep_offsets); + + // [num_threads, batch_size] stores the number of + // samples written by thread range [0, i]. + std::vector> offset_prefix_sum( + num_threads, std::vector(output_dim_batch_size, 0)); + + for (int i = 0; i < output_dim_batch_size; i++) { + int p_reps = (num_samples + num_threads - 1) / num_threads; + offset_prefix_sum[0][i] = rep_offsets[0][i] + p_reps; + for (int j = 1; j < num_threads; j++) { + offset_prefix_sum[j][i] += offset_prefix_sum[j - 1][i]; + offset_prefix_sum[j][i] += rep_offsets[j][i] + p_reps; + } + } + + auto DoWork = [&](int start, int end) { + // Begin simulation. + const auto tfq_for = qsim::SequentialFor(1); + int largest_nq = 1; + Simulator sim = Simulator(tfq_for); + StateSpace ss = StateSpace(tfq_for); + auto sv = ss.Create(largest_nq); + auto scratch = ss.Create(largest_nq); + + for (int i = 0; i < ncircuits.size(); i++) { + int nq = num_qubits[i]; + int j = start > 0 ? offset_prefix_sum[start - 1][i] : 0; + int needed_samples = offset_prefix_sum[start][i] - j; + + if (nq > largest_nq) { + largest_nq = nq; + sv = ss.Create(largest_nq); + scratch = ss.Create(largest_nq); + } + QTSimulator::Parameter param; + param.collect_kop_stat = false; + param.collect_mea_stat = true; + param.normalize_before_mea_gates = true; + + // Track op-wise stats. + std::vector gathered_samples; + int run_samples = 0; + + while (1) { + ss.SetStateZero(sv); + // time since epoch seeds random generator. + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + QTSimulator::RunOnce(param, ncircuits[i], r_seed, ss, sim, scratch, + sv, gathered_samples); + + uint64_t q_ind = 0; + uint64_t mask = 1; + bool val = 0; + while (q_ind < nq) { + val = gathered_samples[0] & mask; + (*output_tensor)( + i, j, static_cast(max_num_qubits - q_ind - 1)) = val; + q_ind++; + mask <<= 1; + } + while (q_ind < max_num_qubits) { + (*output_tensor)( + i, j, static_cast(max_num_qubits - q_ind - 1)) = -2; + q_ind++; + } + + j++; + run_samples++; + + // Check if we have gathered enough samples. + if (run_samples >= needed_samples) { + break; + } + } + } + }; + + // block_size = 1. + tensorflow::thread::ThreadPool::SchedulingParams scheduling_params( + tensorflow::thread::ThreadPool::SchedulingStrategy::kFixedBlockSize, + absl::nullopt, 1); + context->device()->tensorflow_cpu_worker_threads()->workers->ParallelFor( + num_threads, scheduling_params, DoWork); + } +}; + +REGISTER_KERNEL_BUILDER(Name("TfqNoisySamples").Device(tensorflow::DEVICE_CPU), + TfqNoisySamplesOp); + +REGISTER_OP("TfqNoisySamples") + .Input("programs: string") + .Input("symbol_names: string") + .Input("symbol_values: float") + .Input("num_samples: int32") + .Output("samples: int8") + .SetShapeFn([](tensorflow::shape_inference::InferenceContext* c) { + tensorflow::shape_inference::ShapeHandle programs_shape; + TF_RETURN_IF_ERROR(c->WithRank(c->input(0), 1, &programs_shape)); + + tensorflow::shape_inference::ShapeHandle symbol_names_shape; + TF_RETURN_IF_ERROR(c->WithRank(c->input(1), 1, &symbol_names_shape)); + + tensorflow::shape_inference::ShapeHandle symbol_values_shape; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 2, &symbol_values_shape)); + + tensorflow::shape_inference::ShapeHandle num_samples_shape; + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 1, &num_samples_shape)); + + // [batch_size, n_samples, largest_n_qubits] + c->set_output( + 0, c->MakeShape( + {c->Dim(programs_shape, 0), + tensorflow::shape_inference::InferenceContext::kUnknownDim, + tensorflow::shape_inference::InferenceContext::kUnknownDim})); + + return tensorflow::Status::OK(); + }); + +} // namespace tfq diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.cc b/tensorflow_quantum/core/src/circuit_parser_qsim.cc index b54b9623f..051d1d878 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.cc +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.cc @@ -665,6 +665,7 @@ tensorflow::Status ParseAppendChannel(const Operation& op, tensorflow::Status NoisyQsimCircuitFromProgram(const Program& program, const SymbolMap& param_map, const int num_qubits, + const bool add_tmeasures, NoisyQsimCircuit* ncircuit) { // Special case empty. ncircuit->num_qubits = num_qubits; @@ -702,6 +703,17 @@ tensorflow::Status NoisyQsimCircuitFromProgram(const Program& program, time++; } + // Optionally add terminal measurements. + if (add_tmeasures) { + std::vector all_qbs(num_qubits); + std::iota(all_qbs.begin(), all_qbs.end(), 0); + ncircuit->channels.push_back( + {{qsim::KrausOperator::kMeasurement, + 1, + 1.0, + {qsim::gate::Measurement::Create(time, all_qbs)}}}); + } + return Status::OK(); } diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.h b/tensorflow_quantum/core/src/circuit_parser_qsim.h index 7884353ca..dd7dc06d9 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.h +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.h @@ -75,13 +75,15 @@ tensorflow::Status QsimCircuitFromProgram( std::vector* metdata = nullptr); // parse a serialized Cirq program into a qsim representation. -// ingests a Cirq Circuit proto and produces a resolved Noisy qsim Circuit, +// ingests a Cirq Circuit proto and produces a resolved Noisy qsim Circuit. +// If add_tmeasures is true then terminal measurements are added on all +// qubits. // Note: no metadata or fused circuits are produced as the qsim api for // noisy simulation appears to take care of a lot of this for us. tensorflow::Status NoisyQsimCircuitFromProgram( const cirq::google::api::v2::Program& program, const absl::flat_hash_map>& param_map, - const int num_qubits, + const int num_qubits, const bool add_tmeasures, qsim::NoisyCircuit>* ncircuit); // parse a serialized pauliTerm from a larger cirq.Paulisum proto diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc b/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc index 6ea256af6..3ea4a0019 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc +++ b/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc @@ -1225,11 +1225,13 @@ TEST(QsimCircuitParserTest, CompoundCircuit) { NoisyQsimCircuit test_circuit; - ASSERT_EQ(NoisyQsimCircuitFromProgram(program_proto, {}, 2, &test_circuit), - tensorflow::Status::OK()); + ASSERT_EQ( + NoisyQsimCircuitFromProgram(program_proto, {}, 2, true, &test_circuit), + tensorflow::Status::OK()); AssertChannelEqual(test_circuit.channels[0], ref_chan); AssertOneQubitEqual(test_circuit.channels[1][0].ops[0], ref_gate); - ASSERT_EQ(test_circuit.channels.size(), 2); + ASSERT_EQ(test_circuit.channels.size(), + 3); // 2 gates + 1 layer of measurement. ASSERT_EQ(test_circuit.num_qubits, 2); } @@ -1266,8 +1268,9 @@ TEST(QsimCircuitParserTest, AsymmetricDepolarizing) { NoisyQsimCircuit test_circuit; - ASSERT_EQ(NoisyQsimCircuitFromProgram(program_proto, {}, 1, &test_circuit), - tensorflow::Status::OK()); + ASSERT_EQ( + NoisyQsimCircuitFromProgram(program_proto, {}, 1, false, &test_circuit), + tensorflow::Status::OK()); AssertChannelEqual(test_circuit.channels[0], reference); ASSERT_EQ(test_circuit.channels.size(), 1); ASSERT_EQ(test_circuit.num_qubits, 1); @@ -1301,8 +1304,9 @@ TEST(QsimCircuitParserTest, Depolarizing) { NoisyQsimCircuit test_circuit; - ASSERT_EQ(NoisyQsimCircuitFromProgram(program_proto, {}, 1, &test_circuit), - tensorflow::Status::OK()); + ASSERT_EQ( + NoisyQsimCircuitFromProgram(program_proto, {}, 1, false, &test_circuit), + tensorflow::Status::OK()); AssertChannelEqual(test_circuit.channels[0], reference); ASSERT_EQ(test_circuit.channels.size(), 1); ASSERT_EQ(test_circuit.num_qubits, 1); @@ -1315,8 +1319,9 @@ TEST(QsimCircuitParserTest, NoisyEmpty) { Moment* moments_proto = circuit_proto->add_moments(); NoisyQsimCircuit test_circuit; - ASSERT_EQ(NoisyQsimCircuitFromProgram(program_proto, {}, 0, &test_circuit), - tensorflow::Status::OK()); + ASSERT_EQ( + NoisyQsimCircuitFromProgram(program_proto, {}, 0, false, &test_circuit), + tensorflow::Status::OK()); ASSERT_EQ(test_circuit.channels.size(), 0); ASSERT_EQ(test_circuit.num_qubits, 0); } @@ -1333,9 +1338,10 @@ TEST(QsimCircuitParserTest, NoisyBadProto) { gate_proto->set_id("ABCDEFG"); NoisyQsimCircuit test_circuit; - ASSERT_EQ(NoisyQsimCircuitFromProgram(program_proto, {}, 1, &test_circuit), - tensorflow::Status(tensorflow::error::INVALID_ARGUMENT, - "Could not parse channel id: ABCDEFG")); + ASSERT_EQ( + NoisyQsimCircuitFromProgram(program_proto, {}, 1, false, &test_circuit), + tensorflow::Status(tensorflow::error::INVALID_ARGUMENT, + "Could not parse channel id: ABCDEFG")); } TEST(QsimCircuitParserTest, CircuitFromPauliTermPauli) { diff --git a/tensorflow_quantum/core/src/util_qsim.h b/tensorflow_quantum/core/src/util_qsim.h index 7e9e39f6d..f6bc81041 100644 --- a/tensorflow_quantum/core/src/util_qsim.h +++ b/tensorflow_quantum/core/src/util_qsim.h @@ -394,6 +394,45 @@ static void BalanceTrajectory(const std::vector>& num_samples, } } +// Simpler case of TrajectoryBalance where num_samples is fixed +// across all circuits. +static void BalanceTrajectory(const int& num_samples, const int& num_threads, + std::vector>* thread_offsets) { + std::vector height(num_threads, 0); + + int prev_max_height = -1; + for (int j = 0; j < (*thread_offsets)[0].size(); j++) { + int run_ceiling = ((num_samples + num_threads - 1) / num_threads); + int num_lo = num_threads * run_ceiling - num_samples; + int num_hi = num_threads - num_lo; + int cur_max = prev_max_height; + for (int i = 0; i < num_threads; i++) { + if (height[i] == cur_max && num_lo) { + // previously had extra work on this thread and + // have remaining low budget to give. + height[i]++; + (*thread_offsets)[i][j] = -1; + num_lo--; + } else if (height[i] == cur_max - 1 && num_hi) { + // previously had less work on this thread and + // remaining high budget to give. + height[i] += 2; + (*thread_offsets)[i][j] = 0; + num_hi--; + } else if (num_hi) { + height[i] += 2; + (*thread_offsets)[i][j] = 0; + num_hi--; + } else { + height[i]++; + (*thread_offsets)[i][j] = -1; + num_lo--; + } + prev_max_height = std::max(height[i], prev_max_height); + } + } +} + } // namespace tfq #endif // UTIL_QSIM_H_ diff --git a/tensorflow_quantum/core/src/util_qsim_test.cc b/tensorflow_quantum/core/src/util_qsim_test.cc index 82160c5f9..cd3cb13b5 100644 --- a/tensorflow_quantum/core/src/util_qsim_test.cc +++ b/tensorflow_quantum/core/src/util_qsim_test.cc @@ -714,5 +714,36 @@ TEST(UtilQsimTest, BalanceTrajectoryFewHigh) { AssertWellBalanced(n_reps, num_threads, offsets); } +TEST(UtilQsimTest, BalanceTrajectory1D) { + const int n_reps = 100; + const int num_threads = 5; + // [num_threads, n_reps.size()] + std::vector> offsets = {{0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}}; + + std::vector> tmp(offsets[0].size(), + std::vector(2, n_reps)); + BalanceTrajectory(n_reps, num_threads, &offsets); + AssertWellBalanced(tmp, num_threads, offsets); +} + +TEST(UtilQsimTest, BalanceTrajectory1D_2) { + const int n_reps = 11; + const int num_threads = 10; + // [num_threads, n_reps.size()] + std::vector> offsets = { + {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}}; + + std::vector> tmp(offsets[0].size(), + std::vector(2, n_reps)); + BalanceTrajectory(n_reps, num_threads, &offsets); + AssertWellBalanced(tmp, num_threads, offsets); +} + } // namespace } // namespace tfq diff --git a/tensorflow_quantum/python/util.py b/tensorflow_quantum/python/util.py index 1373a1d8c..4ed47fc55 100644 --- a/tensorflow_quantum/python/util.py +++ b/tensorflow_quantum/python/util.py @@ -180,7 +180,8 @@ def random_circuit_resolver_batch(qubits, # skip adding gates in small case. continue locs = tuple(random.sample(qubits, n_qubits)) - if isinstance(op, cirq.IdentityGate): + if isinstance(op, cirq.IdentityGate) or \ + any(isinstance(op, x) for x in _SUPPORTED_CHANNELS): circuit[:i] += op.on(*locs) continue full_gate = (op**np.random.random()).on(*locs) From 69bbd4eb687ee4bdcd42a41709f2879e99c61f8d Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Wed, 24 Mar 2021 21:26:47 -0700 Subject: [PATCH 2/3] format fix. --- .../core/ops/noise/noisy_samples_op_test.py | 98 ++++++++----------- 1 file changed, 43 insertions(+), 55 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py index 0495dc768..0727947e3 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py @@ -56,85 +56,80 @@ def test_simulate_samples_inputs(self): with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'rank 1. Got rank 2'): # programs tensor has the wrong shape. - noisy_samples_op.samples( - util.convert_to_tensor([circuit_batch]), symbol_names, - symbol_values_array, [num_samples]) + noisy_samples_op.samples(util.convert_to_tensor([circuit_batch]), + symbol_names, symbol_values_array, + [num_samples]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'rank 1. Got rank 2'): # symbol_names tensor has the wrong shape. - noisy_samples_op.samples( - util.convert_to_tensor(circuit_batch), np.array([symbol_names]), - symbol_values_array, [num_samples]) + noisy_samples_op.samples(util.convert_to_tensor(circuit_batch), + np.array([symbol_names]), + symbol_values_array, [num_samples]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'rank 2. Got rank 3'): # symbol_values tensor has the wrong shape. - noisy_samples_op.samples( - util.convert_to_tensor(circuit_batch), symbol_names, - np.array([symbol_values_array]), [num_samples]) + noisy_samples_op.samples(util.convert_to_tensor(circuit_batch), + symbol_names, + np.array([symbol_values_array]), + [num_samples]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'rank 2. Got rank 1'): # symbol_values tensor has the wrong shape 2. - noisy_samples_op.samples( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array[0], [num_samples]) + noisy_samples_op.samples(util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array[0], + [num_samples]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'rank 1. Got rank 2'): # num_samples tensor has the wrong shape. - noisy_samples_op.samples( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, [[num_samples]]) + noisy_samples_op.samples(util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array, + [[num_samples]]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Unparseable proto'): # programs tensor has the right type, but invalid value. - noisy_samples_op.samples(['junk'] * batch_size, - symbol_names, - symbol_values_array, - [num_samples]) + noisy_samples_op.samples(['junk'] * batch_size, symbol_names, + symbol_values_array, [num_samples]) with self.assertRaisesRegex(tf.errors.InvalidArgumentError, 'Could not find symbol in parameter map'): # symbol_names tensor has the right type, but invalid value. - noisy_samples_op.samples( - util.convert_to_tensor(circuit_batch), ['junk'], - symbol_values_array, [num_samples]) + noisy_samples_op.samples(util.convert_to_tensor(circuit_batch), + ['junk'], symbol_values_array, + [num_samples]) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # programs tensor has the wrong type. - noisy_samples_op.samples([1] * batch_size, - symbol_names, - symbol_values_array, - [num_samples]) + noisy_samples_op.samples([1] * batch_size, symbol_names, + symbol_values_array, [num_samples]) with self.assertRaisesRegex(TypeError, 'Cannot convert'): # programs tensor has the wrong type. - noisy_samples_op.samples( - util.convert_to_tensor(circuit_batch), [1], symbol_values_array, - [num_samples]) + noisy_samples_op.samples(util.convert_to_tensor(circuit_batch), [1], + symbol_values_array, [num_samples]) with self.assertRaisesRegex(tf.errors.UnimplementedError, 'Cast string to float is not supported'): # programs tensor has the wrong type. - noisy_samples_op.samples( - util.convert_to_tensor(circuit_batch), symbol_names, - [['junk']] * batch_size, [num_samples]) + noisy_samples_op.samples(util.convert_to_tensor(circuit_batch), + symbol_names, [['junk']] * batch_size, + [num_samples]) with self.assertRaisesRegex(Exception, 'junk'): # num_samples tensor has the wrong shape. - noisy_samples_op.samples( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array, ['junk']) + noisy_samples_op.samples(util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array, + ['junk']) with self.assertRaisesRegex(TypeError, 'missing'): # too few tensors. # pylint: disable=no-value-for-parameter - noisy_samples_op.samples( - util.convert_to_tensor(circuit_batch), symbol_names, - symbol_values_array) + noisy_samples_op.samples(util.convert_to_tensor(circuit_batch), + symbol_names, symbol_values_array) # pylint: enable=no-value-for-parameter with self.assertRaisesRegex(tf.errors.InvalidArgumentError, @@ -182,15 +177,13 @@ def test_simulate_consistency(self, batch_size, n_qubits, noisy): n_samples = 10000 op_samples = noisy_samples_op.samples( - util.convert_to_tensor(circuit_batch), - symbol_names, symbol_values_array, [n_samples]).to_list() + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, [n_samples]).to_list() op_hists = self._compute_hists(op_samples, n_qubits) cirq_samples = batch_util.batch_sample( - circuit_batch, - resolver_batch, - n_samples, + circuit_batch, resolver_batch, n_samples, cirq.DensityMatrixSimulator() if noisy else cirq.Simulator()) cirq_hists = self._compute_hists(cirq_samples, n_qubits) @@ -223,14 +216,13 @@ def test_single_channel(self, channel): n_samples = (2**n_qubits) * 1000 op_samples = noisy_samples_op.samples( - util.convert_to_tensor(circuit_batch), - symbol_names, symbol_values_array, [n_samples]).to_list() + util.convert_to_tensor(circuit_batch), symbol_names, + symbol_values_array, [n_samples]).to_list() op_hists = self._compute_hists(op_samples, n_qubits) - cirq_samples = batch_util.batch_sample( - circuit_batch, - resolver_batch, - n_samples, cirq.DensityMatrixSimulator()) + cirq_samples = batch_util.batch_sample(circuit_batch, resolver_batch, + n_samples, + cirq.DensityMatrixSimulator()) cirq_hists = self._compute_hists(cirq_samples, n_qubits) for a, b in zip(op_hists, cirq_hists): @@ -264,8 +256,6 @@ def test_correct_padding(self): symbol_names, symbol_values_array, [n_samples]).to_list() a_reps = np.asarray(op_samples[:2]) b_reps = np.asarray(op_samples[2:]) - print(a_reps) - print(b_reps) self.assertEqual(a_reps.shape, (2, 10, 5)) self.assertEqual(b_reps.shape, (2, 10, 6)) @@ -277,8 +267,7 @@ def test_correctness_empty(self): empty_n_samples = tf.convert_to_tensor([1], dtype=tf.int32) out = noisy_samples_op.samples(empty_circuit, empty_symbols, - empty_values, - empty_n_samples) + empty_values, empty_n_samples) expected = np.array([[[]]], dtype=np.int8) self.assertAllClose(out.to_tensor(), expected) @@ -291,8 +280,7 @@ def test_correctness_no_circuit(self): empty_n_samples = tf.convert_to_tensor([1], dtype=tf.int32) out = noisy_samples_op.samples(empty_circuit, empty_symbols, - empty_values, - empty_n_samples) + empty_values, empty_n_samples) self.assertShapeEqual(np.zeros((0, 0, 0)), out.to_tensor()) From 616f12b2811cf0b5c1ca3ddb812203e0e8c07ffe Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Fri, 26 Mar 2021 00:16:07 -0700 Subject: [PATCH 3/3] Jae feedback. --- .../core/ops/noise/noisy_samples_op_test.py | 17 ++++++++++------- tensorflow_quantum/core/src/util_qsim_test.cc | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py b/tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py index 0727947e3..684c3602c 100644 --- a/tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py +++ b/tensorflow_quantum/core/ops/noise/noisy_samples_op_test.py @@ -120,7 +120,7 @@ def test_simulate_samples_inputs(self): [num_samples]) with self.assertRaisesRegex(Exception, 'junk'): - # num_samples tensor has the wrong shape. + # num_samples tensor has the wrong type. noisy_samples_op.samples(util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, ['junk']) @@ -236,18 +236,21 @@ def test_correct_padding(self): qubits1 = cirq.GridQubit.rect(1, n_qubits) qubits2 = cirq.GridQubit.rect(1, n_qubits + 1) - circuit_batch1, resolver_batch = \ + circuit_batch1, resolver_batch1 = \ util.random_circuit_resolver_batch( qubits1, batch_size, include_channels=True) - circuit_batch2, _ = \ + circuit_batch2, resolver_batch2 = \ util.random_circuit_resolver_batch( qubits2, batch_size, include_channels=True) - symbol_values_array = np.array( - [[resolver[symbol] - for symbol in symbol_names] - for resolver in resolver_batch] * 2) + p1 = [[resolver[symbol] + for symbol in symbol_names] + for resolver in resolver_batch1] + p2 = [[resolver[symbol] + for symbol in symbol_names] + for resolver in resolver_batch2] + symbol_values_array = np.array(p1 + p2) n_samples = 10 diff --git a/tensorflow_quantum/core/src/util_qsim_test.cc b/tensorflow_quantum/core/src/util_qsim_test.cc index cd3cb13b5..7563af3d3 100644 --- a/tensorflow_quantum/core/src/util_qsim_test.cc +++ b/tensorflow_quantum/core/src/util_qsim_test.cc @@ -717,7 +717,7 @@ TEST(UtilQsimTest, BalanceTrajectoryFewHigh) { TEST(UtilQsimTest, BalanceTrajectory1D) { const int n_reps = 100; const int num_threads = 5; - // [num_threads, n_reps.size()] + // [num_threads, batch_size] std::vector> offsets = {{0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0}, @@ -733,7 +733,7 @@ TEST(UtilQsimTest, BalanceTrajectory1D) { TEST(UtilQsimTest, BalanceTrajectory1D_2) { const int n_reps = 11; const int num_threads = 10; - // [num_threads, n_reps.size()] + // [num_threads, batch_size] std::vector> offsets = { {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0},