def __init__(self, backend='noiseless', differentiator=None, **kwargs): """Instantiate this Layer. Create a layer that will output expectation values gained from simulating a quantum circuit. Args: backend: Optional Backend to use to simulate states. Defaults to the 'noiseless' simulator, options include {'noiseless', 'noisy'}. In the noisy case a `repetitions` call argument must be provided. Users may also specify a preconfigured cirq object to use instead, which must inherit `cirq.sim.simulator.SimulatesExpectationValues`. differentiator: Optional Differentiator to use to calculate analytic derivative values of given operators_to_measure and circuit, which must inherit `tfq.differentiators.Differentiator` and implements `differentiate_analytic` method. Defaults to None, which uses `tfq.differentiators.ParameterShift()`. If `backend` is also 'noiseless' then default is `tfq.differentiators.Adjoint`. """ super().__init__(**kwargs) # Ingest backend. if not isinstance( backend, cirq.sim.simulator.SimulatesExpectationValues) and \ isinstance(backend, cirq.Sampler): raise TypeError("Backend implements cirq.Sampler but not " "cirq.sim.simulator.SimulatesExpectationValues. " "Please use SampledExpectation instead.") used_op = None self.noisy = False if backend == 'noiseless': backend = None # Ingest differentiator. if differentiator is None: differentiator = parameter_shift.ParameterShift() if backend is None: differentiator = adjoint.Adjoint() if not isinstance(differentiator, diff.Differentiator): raise TypeError("Differentiator must inherit from " "tfq.differentiators.Differentiator") if backend == 'noisy': used_op = noisy_expectation_op.expectation self._expectation_op = differentiator.generate_differentiable_op( sampled_op=used_op) self.noisy = True else: used_op = circuit_execution_ops.get_expectation_op(backend=backend) self._expectation_op = differentiator.generate_differentiable_op( analytic_op=used_op) self._w = None
def test_parameter_shift_sampled(self): """Test if ParameterShift.differentiate_sampled doesn't crash before running.""" programs, names, values, ops, n_samples, true_f, true_g = \ _simple_op_inputs() ps = parameter_shift.ParameterShift() op = ps.generate_differentiable_op( sampled_op=circuit_execution_ops.get_sampled_expectation_op()) with tf.GradientTape() as g: g.watch(values) expectations = op(programs, names, values, ops, n_samples) grads = g.gradient(expectations, values) self.assertAllClose(expectations, true_f, atol=1e-1, rtol=1e-1) self.assertAllClose(grads, true_g, atol=1e-1, rtol=1e-1)
def __init__(self, backend='noiseless', differentiator=None, **kwargs): """Instantiate this Layer. Create a layer that will output expectation values gained from simulating a quantum circuit. Args: backend: Optional Backend to use to simulate states. Can be either {'noiseless', 'noisy'} users may also specify a preconfigured cirq simulation object to use instead, which must inherit `cirq.Sampler`. differentiator: Optional Differentiator to use to calculate analytic derivative values of given operators_to_measure and circuit, which must inherit `tfq.differentiators.Differentiator`. Defaults to `parameter_shift.ParameterShift()` (None argument). """ super().__init__(**kwargs) # Ingest differentiator. if differentiator is None: differentiator = parameter_shift.ParameterShift() if not isinstance(differentiator, diff.Differentiator): raise TypeError("Differentiator must inherit from " "tfq.differentiators.Differentiator") # Ingest backend. if not isinstance(backend, cirq.Sampler) and \ isinstance(backend, cirq.SimulatesFinalState): raise TypeError( "Backend implements cirq.SimulatesFinalState but " "not cirq.Sampler. Please use Expectation instead.") used_op = None if backend == 'noiseless': used_op = circuit_execution_ops.get_sampled_expectation_op( backend=None) elif backend == 'noisy': used_op = noisy_sampled_expectation_op.sampled_expectation else: used_op = circuit_execution_ops.get_sampled_expectation_op( backend=backend) self._expectation_op = differentiator.generate_differentiable_op( sampled_op=used_op) self._w = None
class GradientBenchmarksTest(tf.test.TestCase, parameterized.TestCase): """Test the Gradient benchmarking class.""" @parameterized.parameters( list( util.kwargs_cartesian_product( **{ 'diff': [ linear_combination.ForwardDifference(), linear_combination.CentralDifference(), parameter_shift.ParameterShift(), stochastic_differentiator.SGDifferentiator(), ], 'params': [TEST_PARAMS_1, TEST_PARAMS_2] }))) def testBenchmarkGradient(self, diff, params): """Test that op constructs and runs correctly.""" bench_name = "GradientBenchmarks.{}_{}_{}_{}_{}".format( diff.__class__.__name__, params.n_qubits, params.n_moments, params.batch_size, params.n_symbols) proto_file_path = os.path.join(SRC, "reports/", "{}".format(bench_name)) self.addCleanup(os.remove, proto_file_path) bench = GradientBenchmarks(params=params) bench.setup() bench._benchmark_tfq_differentiator(diff, params) res = benchmark_util.read_benchmark_entry(proto_file_path) self.assertEqual(res.name, bench_name) self.assertEqual( res.extras.get("n_qubits").double_value, params.n_qubits) self.assertEqual( res.extras.get("n_moments").double_value, params.n_moments) self.assertEqual( res.extras.get("op_density").double_value, params.op_density) assert hasattr(res, 'iters') assert hasattr(res, 'wall_time')
import tensorflow as tf from absl.testing import parameterized import cirq from tensorflow_quantum.python import util from tensorflow_quantum.python.differentiators import adjoint from tensorflow_quantum.python.differentiators import linear_combination from tensorflow_quantum.python.differentiators import parameter_shift from tensorflow_quantum.core.ops import circuit_execution_ops, batch_util ANALYTIC_DIFFS = [ linear_combination.ForwardDifference(grid_spacing=0.0001), linear_combination.ForwardDifference(error_order=2, grid_spacing=0.0001), linear_combination.CentralDifference(grid_spacing=0.0001), linear_combination.CentralDifference(error_order=4, grid_spacing=0.0001), parameter_shift.ParameterShift(), ] SAMPLED_DIFFS = [ linear_combination.ForwardDifference(grid_spacing=0.05), linear_combination.CentralDifference(grid_spacing=0.05), parameter_shift.ParameterShift(), ] SAMPLED_DIFFS_TOLS = [0.5, 0.5, 0.2] ANALYTIC_OPS = [ circuit_execution_ops.get_expectation_op(cirq.sim.Simulator()), # WF circuit_execution_ops.get_expectation_op() # C++ ]
def benchmark_parameter_shift(self): """Benchmark the parameter shift gradient method.""" diff = parameter_shift.ParameterShift() self._benchmark_tfq_differentiator(diff, self.params)
def test_get_gradient_circuits(self): """Test that the correct objects are returned.""" diff = parameter_shift.ParameterShift() # Circuits to differentiate. symbols = [sympy.Symbol("s0"), sympy.Symbol("s1")] q0 = cirq.GridQubit(0, 0) q1 = cirq.GridQubit(1, 2) input_programs = util.convert_to_tensor([ cirq.Circuit( cirq.X(q0)**symbols[0], cirq.Y(q0)**symbols[0], cirq.ry(symbols[1])(q1)), cirq.Circuit(cirq.Y(q1)**symbols[1]), ]) input_symbol_names = tf.constant([str(s) for s in symbols]) input_symbol_values = tf.constant([[1.5, -2.7], [-0.3, 0.9]]) # First, for each symbol `s`, check how many times `s` appears in each # program `p`, `n_ps`. Let `n_param_gates` be the maximum of `n_ps` over # all symbols and programs. Then, the shape of `batch_programs` will be # [n_programs, n_symbols * n_param_gates * n_shifts], where `n_shifts` # is 2 because we decompose into gates with 2 eigenvalues. For row index # `p` we have for column indices between `i * n_param_gates * n_shifts` # and `(i + 1) * n_param_gates * n_shifts`, the first `n_pi * 2` # programs are parameter shifted versions of `input_programs[p]` and the # remaining programs are empty. # Here, `n_param_gates` is 2. impurity_symbol_name = "_impurity_for_param_shift" impurity_symbol = sympy.Symbol(impurity_symbol_name) expected_batch_programs_0 = util.convert_to_tensor([ cirq.Circuit( cirq.X(q0)**impurity_symbol, cirq.Y(q0)**symbols[0], cirq.ry(symbols[1])(q1)), cirq.Circuit( cirq.X(q0)**impurity_symbol, cirq.Y(q0)**symbols[0], cirq.ry(symbols[1])(q1)), cirq.Circuit( cirq.X(q0)**symbols[0], cirq.Y(q0)**impurity_symbol, cirq.ry(symbols[1])(q1)), cirq.Circuit( cirq.X(q0)**symbols[0], cirq.Y(q0)**impurity_symbol, cirq.ry(symbols[1])(q1)), cirq.Circuit( cirq.X(q0)**symbols[0], cirq.Y(q0)**symbols[0], cirq.ry(impurity_symbol)(q1)), cirq.Circuit( cirq.X(q0)**symbols[0], cirq.Y(q0)**symbols[0], cirq.ry(impurity_symbol)(q1)), cirq.Circuit(), cirq.Circuit() ]) expected_batch_programs_1 = util.convert_to_tensor([ cirq.Circuit(), cirq.Circuit(), cirq.Circuit(), cirq.Circuit(), cirq.Circuit(cirq.Y(q1)**impurity_symbol), cirq.Circuit(cirq.Y(q1)**impurity_symbol), cirq.Circuit(), cirq.Circuit() ]) expected_batch_programs = tf.stack( [expected_batch_programs_0, expected_batch_programs_1]) # The new symbols are the old ones, with an extra used for shifting. expected_new_symbol_names = tf.concat( [input_symbol_names, tf.constant([impurity_symbol_name])], 0) # The batch symbol values are the input symbol values, tiled and with # shifted values appended. Locations that have empty programs should # also have zero for the shift. # The shifted values are the original value plus 1/2 divided by the # `exponent_scalar` of the gate. expected_batch_symbol_values = tf.constant( [[[1.5, -2.7, 1.5 + 0.5], [1.5, -2.7, 1.5 - 0.5], [1.5, -2.7, 1.5 + 0.5], [1.5, -2.7, 1.5 - 0.5], [1.5, -2.7, -2.7 + np.pi / 2], [1.5, -2.7, -2.7 - np.pi / 2], [1.5, -2.7, -2.7], [1.5, -2.7, -2.7]], [[-0.3, 0.9, -0.3], [-0.3, 0.9, -0.3], [-0.3, 0.9, -0.3], [-0.3, 0.9, -0.3], [-0.3, 0.9, 0.9 + 0.5], [-0.3, 0.9, 0.9 - 0.5], [-0.3, 0.9, 0.9], [-0.3, 0.9, 0.9]]]) # Empty program locations are given zero weight. expected_batch_weights = tf.constant( [[[np.pi / 2, -np.pi / 2, np.pi / 2, -np.pi / 2], [0.5, -0.5, 0.0, 0.0]], [[0.0, 0.0, 0.0, 0.0], [np.pi / 2, -np.pi / 2, 0.0, 0.0]]]) expected_batch_mapper = tf.constant([[[0, 1, 2, 3], [4, 5, 6, 7]], [[0, 1, 2, 3], [4, 5, 6, 7]]]) (test_batch_programs, test_new_symbol_names, test_batch_symbol_values, test_batch_weights, test_batch_mapper) = diff.get_gradient_circuits( input_programs, input_symbol_names, input_symbol_values) for i in range(tf.shape(input_programs)[0]): self.assertAllEqual(util.from_tensor(expected_batch_programs[i]), util.from_tensor(test_batch_programs[i])) self.assertAllEqual(expected_new_symbol_names, test_new_symbol_names) self.assertAllClose(expected_batch_symbol_values, test_batch_symbol_values, atol=1e-5) self.assertAllClose(expected_batch_weights, test_batch_weights, atol=1e-5) self.assertAllEqual(expected_batch_mapper, test_batch_mapper)
def __init__(self, model_circuit, operators, *, repetitions=None, sample_based=None, differentiator=None, **kwargs): """Instantiate this layer. Create a layer that will output noisy expectation values of the given operators when fed quantum data to it's input layer. This layer will take two input tensors, one representing a quantum data source (these circuits must not contain any symbols) and the other representing control parameters for the model circuit that gets appended to the datapoints. model_circuit: `cirq.Circuit` containing `sympy.Symbols` that will be used as the model which will be fed quantum data inputs. operators: `cirq.PauliSum` or Python `list` of `cirq.PauliSum` objects used as observables at the end of the model circuit. repetitions: Python `int` indicating how many trajectories to use when estimating expectation values. sample_based: Python `bool` indicating whether to use sampling to estimate expectations or analytic calculations with each trajectory. differentiator: Optional `tfq.differentiator` object to specify how gradients of `model_circuit` should be calculated. """ super().__init__(**kwargs) # Ingest model_circuit. if not isinstance(model_circuit, cirq.Circuit): raise TypeError("model_circuit must be a cirq.Circuit object." " Given: ".format(model_circuit)) self._symbols_list = list( sorted(util.get_circuit_symbols(model_circuit))) self._symbols = tf.constant([str(x) for x in self._symbols_list]) self._circuit = util.convert_to_tensor([model_circuit]) if len(self._symbols_list) == 0: raise ValueError("model_circuit has no sympy.Symbols. Please " "provide a circuit that contains symbols so " "that their values can be trained.") # Ingest operators. if isinstance(operators, (cirq.PauliString, cirq.PauliSum)): operators = [operators] if not isinstance(operators, (list, np.ndarray, tuple)): raise TypeError("operators must be a cirq.PauliSum or " "cirq.PauliString, or a list, tuple, " "or np.array containing them. " "Got {}.".format(type(operators))) if not all([ isinstance(op, (cirq.PauliString, cirq.PauliSum)) for op in operators ]): raise TypeError("Each element in operators to measure " "must be a cirq.PauliString" " or cirq.PauliSum") self._operators = util.convert_to_tensor([operators]) # Ingest and promote repetitions. if repetitions is None: raise ValueError("Value for repetitions must be provided when " "using noisy simulation.") if not isinstance(repetitions, numbers.Integral): raise TypeError("repetitions must be a positive integer value." " Given: ".format(repetitions)) if repetitions <= 0: raise ValueError("Repetitions must be greater than zero.") self._repetitions = tf.constant( [[repetitions for _ in range(len(operators))]], dtype=tf.dtypes.int32) # Ingest differentiator. if differentiator is None: differentiator = parameter_shift.ParameterShift() # Ingest and promote sample based. if sample_based is None: raise ValueError("Please specify sample_based=False for analytic " "calculations based on monte-carlo trajectories," " or sampled_based=True for measurement based " "noisy estimates.") if not isinstance(sample_based, bool): raise TypeError("sample_based must be either True or False." " received: {}".format(type(sample_based))) if not sample_based: self._executor = differentiator.generate_differentiable_op( sampled_op=noisy_expectation_op.expectation) else: self._executor = differentiator.generate_differentiable_op( sampled_op=noisy_sampled_expectation_op.sampled_expectation) self._append_layer = elementary.AddCircuit()
def test_no_gradient_circuits(self): """Confirm ParameterShift differentiator has no gradient circuits.""" dif = parameter_shift.ParameterShift() with self.assertRaisesRegex(NotImplementedError, expected_regex="not currently available"): _ = dif.get_gradient_circuits(None, None, None)
class ParameterShiftTest(tf.test.TestCase, parameterized.TestCase): """Test the ParameterShift Differentiator will run end to end.""" def test_parameter_shift_analytic(self): """Test if ParameterShift.differentiate_analytical doesn't crash before running.""" programs, names, values, ops, _, true_f, true_g = \ _simple_op_inputs() ps = parameter_shift.ParameterShift() op = ps.generate_differentiable_op( analytic_op=circuit_execution_ops.get_expectation_op()) with tf.GradientTape() as g: g.watch(values) expectations = op(programs, names, values, ops) grads = g.gradient(expectations, values) self.assertAllClose(expectations, true_f, atol=1e-2, rtol=1e-2) self.assertAllClose(grads, true_g, atol=1e-2, rtol=1e-2) def test_parameter_shift_sampled(self): """Test if ParameterShift.differentiate_sampled doesn't crash before running.""" programs, names, values, ops, n_samples, true_f, true_g = \ _simple_op_inputs() ps = parameter_shift.ParameterShift() op = ps.generate_differentiable_op( sampled_op=circuit_execution_ops.get_sampled_expectation_op()) with tf.GradientTape() as g: g.watch(values) expectations = op(programs, names, values, ops, n_samples) grads = g.gradient(expectations, values) self.assertAllClose(expectations, true_f, atol=1e-1, rtol=1e-1) self.assertAllClose(grads, true_g, atol=1e-1, rtol=1e-1) def test_get_gradient_circuits(self): """Test that the correct objects are returned.""" diff = parameter_shift.ParameterShift() # Circuits to differentiate. symbols = [sympy.Symbol("s0"), sympy.Symbol("s1")] q0 = cirq.GridQubit(0, 0) q1 = cirq.GridQubit(1, 2) input_programs = util.convert_to_tensor([ cirq.Circuit( cirq.X(q0)**symbols[0], cirq.Y(q0)**symbols[0], cirq.ry(symbols[1])(q1)), cirq.Circuit(cirq.Y(q1)**symbols[1]), ]) input_symbol_names = tf.constant([str(s) for s in symbols]) input_symbol_values = tf.constant([[1.5, -2.7], [-0.3, 0.9]]) # First, for each symbol `s`, check how many times `s` appears in each # program `p`, `n_ps`. Let `n_param_gates` be the maximum of `n_ps` over # all symbols and programs. Then, the shape of `batch_programs` will be # [n_programs, n_symbols * n_param_gates * n_shifts], where `n_shifts` # is 2 because we decompose into gates with 2 eigenvalues. For row index # `p` we have for column indices between `i * n_param_gates * n_shifts` # and `(i + 1) * n_param_gates * n_shifts`, the first `n_pi * 2` # programs are parameter shifted versions of `input_programs[p]` and the # remaining programs are empty. # Here, `n_param_gates` is 2. impurity_symbol_name = "_impurity_for_param_shift" impurity_symbol = sympy.Symbol(impurity_symbol_name) expected_batch_programs_0 = util.convert_to_tensor([ cirq.Circuit( cirq.X(q0)**impurity_symbol, cirq.Y(q0)**symbols[0], cirq.ry(symbols[1])(q1)), cirq.Circuit( cirq.X(q0)**impurity_symbol, cirq.Y(q0)**symbols[0], cirq.ry(symbols[1])(q1)), cirq.Circuit( cirq.X(q0)**symbols[0], cirq.Y(q0)**impurity_symbol, cirq.ry(symbols[1])(q1)), cirq.Circuit( cirq.X(q0)**symbols[0], cirq.Y(q0)**impurity_symbol, cirq.ry(symbols[1])(q1)), cirq.Circuit( cirq.X(q0)**symbols[0], cirq.Y(q0)**symbols[0], cirq.ry(impurity_symbol)(q1)), cirq.Circuit( cirq.X(q0)**symbols[0], cirq.Y(q0)**symbols[0], cirq.ry(impurity_symbol)(q1)), cirq.Circuit(), cirq.Circuit() ]) expected_batch_programs_1 = util.convert_to_tensor([ cirq.Circuit(), cirq.Circuit(), cirq.Circuit(), cirq.Circuit(), cirq.Circuit(cirq.Y(q1)**impurity_symbol), cirq.Circuit(cirq.Y(q1)**impurity_symbol), cirq.Circuit(), cirq.Circuit() ]) expected_batch_programs = tf.stack( [expected_batch_programs_0, expected_batch_programs_1]) # The new symbols are the old ones, with an extra used for shifting. expected_new_symbol_names = tf.concat( [input_symbol_names, tf.constant([impurity_symbol_name])], 0) # The batch symbol values are the input symbol values, tiled and with # shifted values appended. Locations that have empty programs should # also have zero for the shift. # The shifted values are the original value plus 1/2 divided by the # `exponent_scalar` of the gate. expected_batch_symbol_values = tf.constant( [[[1.5, -2.7, 1.5 + 0.5], [1.5, -2.7, 1.5 - 0.5], [1.5, -2.7, 1.5 + 0.5], [1.5, -2.7, 1.5 - 0.5], [1.5, -2.7, -2.7 + np.pi / 2], [1.5, -2.7, -2.7 - np.pi / 2], [1.5, -2.7, -2.7], [1.5, -2.7, -2.7]], [[-0.3, 0.9, -0.3], [-0.3, 0.9, -0.3], [-0.3, 0.9, -0.3], [-0.3, 0.9, -0.3], [-0.3, 0.9, 0.9 + 0.5], [-0.3, 0.9, 0.9 - 0.5], [-0.3, 0.9, 0.9], [-0.3, 0.9, 0.9]]]) # Empty program locations are given zero weight. expected_batch_weights = tf.constant( [[[np.pi / 2, -np.pi / 2, np.pi / 2, -np.pi / 2], [0.5, -0.5, 0.0, 0.0]], [[0.0, 0.0, 0.0, 0.0], [np.pi / 2, -np.pi / 2, 0.0, 0.0]]]) expected_batch_mapper = tf.constant([[[0, 1, 2, 3], [4, 5, 6, 7]], [[0, 1, 2, 3], [4, 5, 6, 7]]]) (test_batch_programs, test_new_symbol_names, test_batch_symbol_values, test_batch_weights, test_batch_mapper) = diff.get_gradient_circuits( input_programs, input_symbol_names, input_symbol_values) for i in range(tf.shape(input_programs)[0]): self.assertAllEqual(util.from_tensor(expected_batch_programs[i]), util.from_tensor(test_batch_programs[i])) self.assertAllEqual(expected_new_symbol_names, test_new_symbol_names) self.assertAllClose(expected_batch_symbol_values, test_batch_symbol_values, atol=1e-5) self.assertAllClose(expected_batch_weights, test_batch_weights, atol=1e-5) self.assertAllEqual(expected_batch_mapper, test_batch_mapper) @parameterized.parameters( list( util.kwargs_cartesian_product( **{ 'differentiator': [ parameter_shift.ParameterShift(), ], 'n_qubits': [5], 'n_programs': [3], 'n_ops': [3], 'symbol_names': [['a', 'b']] }))) def test_gradient_circuits_grad_comparison(self, differentiator, n_qubits, n_programs, n_ops, symbol_names): """Test that analytic gradient agrees with the one from grad circuits""" # Get random circuits to check. qubits = cirq.GridQubit.rect(1, n_qubits) circuit_batch, resolver_batch = \ util.random_symbol_circuit_resolver_batch( cirq.GridQubit.rect(1, n_qubits), symbol_names, n_programs) psums = [ util.random_pauli_sums(qubits, 1, n_ops) for _ in circuit_batch ] # Convert to tensors. symbol_names_array = np.array(symbol_names) symbol_values_array = np.array( [[resolver[symbol] for symbol in symbol_names] for resolver in resolver_batch], dtype=np.float32) symbol_names_tensor = tf.convert_to_tensor(symbol_names_array) symbol_values_tensor = tf.convert_to_tensor(symbol_values_array) programs = util.convert_to_tensor(circuit_batch) ops_tensor = util.convert_to_tensor(psums) # Get gradients using expectations of gradient circuits. (batch_programs, new_symbol_names, batch_symbol_values, batch_weights, batch_mapper) = differentiator.get_gradient_circuits( programs, symbol_names_tensor, symbol_values_tensor) analytic_op = circuit_execution_ops.get_expectation_op() batch_pauli_sums = tf.tile(tf.expand_dims(ops_tensor, 1), [1, tf.shape(batch_programs)[1], 1]) n_batch_programs = tf.reduce_prod(tf.shape(batch_programs)) n_symbols = tf.shape(new_symbol_names)[0] batch_expectations = analytic_op( tf.reshape(batch_programs, [n_batch_programs]), new_symbol_names, tf.reshape(batch_symbol_values, [n_batch_programs, n_symbols]), tf.reshape(batch_pauli_sums, [n_batch_programs, n_ops])) batch_expectations = tf.reshape(batch_expectations, tf.shape(batch_pauli_sums)) batch_jacobian = tf.map_fn( lambda x: tf.einsum('km,kmp->kp', x[0], tf.gather(x[1], x[2])), (batch_weights, batch_expectations, batch_mapper), fn_output_signature=tf.float32) grad_manual = tf.reduce_sum(batch_jacobian, -1) # Get gradients using autodiff. differentiator.refresh() differentiable_op = differentiator.generate_differentiable_op( analytic_op=analytic_op) with tf.GradientTape() as g: g.watch(symbol_values_tensor) exact_outputs = differentiable_op(programs, symbol_names_tensor, symbol_values_tensor, ops_tensor) grad_auto = g.gradient(exact_outputs, symbol_values_tensor) self.assertAllClose(grad_manual, grad_auto, atol=1e-5)