def test_sampled_expectation_symbol_input(self): """Test that SampledExpectation only accepts valid permutations of symbols.""" sampled_expectation.SampledExpectation() sampled_expectation.SampledExpectation(backend=cirq.Simulator()) sampled_expectation.SampledExpectation( differentiator=linear_combination.ForwardDifference())
def test_sampled_expectation_type_inputs_error(self): """Test that SampledExpectation errors within Keras call.""" bit = cirq.GridQubit(0, 0) symbol = sympy.Symbol('alpha') test_pstring = cirq.Z(bit) test_psum = cirq.PauliSum.from_pauli_strings([test_pstring]) symb_circuit = cirq.Circuit(cirq.H(bit)**symbol) reg_circuit = cirq.Circuit(cirq.H(bit)) with self.assertRaisesRegex(RuntimeError, expected_regex="repetitions not provided"): sampled_expectation.SampledExpectation()(symb_circuit, symbol_names=[symbol], symbol_values=[[0.5]], operators=test_psum) with self.assertRaisesRegex(Exception, expected_regex="Unknown initializer"): sampled_expectation.SampledExpectation()(reg_circuit, operators=test_psum, initializer='junk', repetitions=1) with self.assertRaisesRegex(Exception, expected_regex="cannot be parsed"): sampled_expectation.SampledExpectation()(reg_circuit, operators=test_psum, repetitions='junk')
def test_dnn_qnn_dnn(self): """Train a fully hybrid network using an SampledExpectation layer. Train the network to output +-5 given an input of 1 or 0. This tests that everything works when SampledExpectation layer is a middle layers. """ bit = cirq.GridQubit(0, 0) symbols = sympy.symbols('x, y, z') circuits = util.convert_to_tensor( [_gen_single_bit_rotation_problem(bit, symbols)] * 2) data_in = np.array([[1], [0]], dtype=np.float32) data_out = np.array([[5], [-5]], dtype=np.float32) classical_input = tf.keras.Input(shape=(1, )) circuit_input = tf.keras.Input(shape=(), dtype=tf.dtypes.string) d1 = tf.keras.layers.Dense(10)(classical_input) d2 = tf.keras.layers.Dense(3)(d1) quantum = sampled_expectation.SampledExpectation()( circuit_input, symbol_names=symbols, symbol_values=d2, operators=cirq.Z(bit), repetitions=5000) d3 = tf.keras.layers.Dense(1)(quantum) model = tf.keras.Model(inputs=[circuit_input, classical_input], outputs=d3) model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.05), loss=tf.keras.losses.mean_squared_error) history = model.fit(x=[circuits, data_in], y=data_out, batch_size=2, epochs=75) self.assertAllClose(history.history['loss'][-1], 0, atol=4)
def test_simple_param_value_input(self): """Train a densely connected hybrid model. This model will put a qubit in the zero or one state from a random state given the input zero or one. """ bit = cirq.GridQubit(0, 0) symbols = sympy.symbols('x y z') circuit = _gen_single_bit_rotation_problem(bit, symbols) inputs = tf.keras.Input(shape=(1, ), dtype=tf.dtypes.float64) datum = tf.keras.Input(shape=(), dtype=tf.dtypes.string) l1 = tf.keras.layers.Dense(10)(inputs) l2 = tf.keras.layers.Dense(3)(l1) outputs = sampled_expectation.SampledExpectation()( datum, symbol_names=symbols, operators=cirq.Z(bit), symbol_values=l2, repetitions=5000) model = tf.keras.Model(inputs=[datum, inputs], outputs=outputs) data_in = np.array([[1], [0]], dtype=np.float32) data_out = np.array([[1], [-1]], dtype=np.float32) model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.05), loss=tf.keras.losses.mean_squared_error) circuits = util.convert_to_tensor([circuit, circuit]) history = model.fit(x=[circuits, data_in], y=data_out, epochs=30) self.assertAllClose(history.history['loss'][-1], 0, atol=0.3)
def test_simple_op_input(self): """Test a simple operator input Learn qubit in the z+ state using two different measurement operators. """ bit = cirq.GridQubit(0, 0) symbols = sympy.symbols('x y z') ops = util.convert_to_tensor([[cirq.Z(bit)], [cirq.Z(bit)]]) n = tf.convert_to_tensor([[5000], [5000]], dtype=tf.int32) circuit = util.convert_to_tensor( [_gen_single_bit_rotation_problem(bit, symbols)] * 2) data_out = tf.convert_to_tensor(np.array([[1], [1]])) op_inp = tf.keras.Input(shape=(1, ), dtype=tf.dtypes.string) n_inp = tf.keras.Input(shape=(1, ), dtype=tf.dtypes.int32) circuit_inp = tf.keras.Input(shape=(), dtype=tf.dtypes.string) circuit_output = sampled_expectation.SampledExpectation()( circuit_inp, symbol_names=symbols, operators=op_inp, repetitions=n_inp) model = tf.keras.Model(inputs=[circuit_inp, op_inp, n_inp], outputs=[circuit_output]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.05), loss=tf.keras.losses.mean_squared_error, ) history = model.fit(x=[circuit, ops, n], y=data_out, batch_size=1, epochs=3) self.assertAllClose(history.history['loss'][-1], 0, atol=1e-2)
def test_sampled_expectation_op_error(self, backend): """Test that expectation errors within underlying ops correctly.""" # Note the expected_regex is left blank here since there is a # discrepancy between the error strings provided between backends. bit = cirq.GridQubit(0, 0) symbol = sympy.Symbol('alpha') test_pstring = cirq.Z(bit) test_psum = cirq.PauliSum.from_pauli_strings([test_pstring]) symb_circuit = cirq.Circuit(cirq.H(bit)**symbol) reg_circuit = cirq.Circuit(cirq.H(bit)) with self.assertRaisesRegex(Exception, expected_regex="pauli"): # Operators has wrong rank. Parse error. sampled_expectation.SampledExpectation(backend=backend)( [reg_circuit], operators=util.convert_to_tensor([test_psum]), repetitions=1) with self.assertRaisesRegex(Exception, expected_regex="symbol_values"): # symbol_values has wrong rank. sampled_expectation.SampledExpectation(backend=backend)( [symb_circuit], symbol_names=[symbol], symbol_values=[0.5], operators=test_psum, repetitions=1) with self.assertRaisesRegex(Exception, expected_regex="pauli"): # Wrong batch size for pauli operators. sampled_expectation.SampledExpectation(backend=backend)( symb_circuit, symbol_names=[symbol], operators=[[test_psum], [test_psum]], repetitions=1) with self.assertRaisesRegex(Exception, expected_regex="pauli"): # Wrong batch size for pauli operators. sampled_expectation.SampledExpectation(backend=backend)( reg_circuit, operators=[[test_psum], [test_psum]], repetitions=1) with self.assertRaisesRegex(Exception, expected_regex="0"): # Wrong repetitions. sampled_expectation.SampledExpectation(backend=backend)( reg_circuit, operators=test_psum, repetitions=-1) with self.assertRaisesRegex(Exception, expected_regex=""): # Wrong second dimension size for repetitions & pauli operators. sampled_expectation.SampledExpectation(backend=backend)( reg_circuit, operators=test_psum, repetitions=[5, 4, 3]) with self.assertRaisesRegex(Exception, expected_regex=""): # Wrong batch_size for symbol values. sampled_expectation.SampledExpectation(backend=backend)( [reg_circuit], symbol_names=[symbol], symbol_values=np.zeros((3, 1)), operators=test_psum, repetitions=5)
def test_sampled_expectation_instantiate_error(self): """Test that SampledExpectation errors with bad inputs.""" class MySim(cirq.SimulatesFinalState): """Class to test sampler detection in Expectation.""" def simulate_sweep(self): """Do nothing.""" return with self.assertRaisesRegex(TypeError, expected_regex="Expectation"): sampled_expectation.SampledExpectation(backend=MySim()) with self.assertRaisesRegex(TypeError, expected_regex="Sampler or None"): sampled_expectation.SampledExpectation(backend='junk') with self.assertRaisesRegex( TypeError, expected_regex="tfq.differentiators.Differentiator"): sampled_expectation.SampledExpectation(differentiator='junk')
def test_sampled_expectation_simple_tf_train(self): """Train a layer using standard tf (not keras).""" bit = cirq.GridQubit(0, 0) circuit = cirq.Circuit(cirq.rx(sympy.Symbol('theta'))(bit)) layer = sampled_expectation.SampledExpectation() optimizer = tf.optimizers.Adam(learning_rate=0.05) for _ in range(10): with tf.GradientTape() as tape: circuit_out = layer(circuit, symbol_names=['theta'], operators=cirq.Z(bit), repetitions=100) mse = tf.square(tf.reduce_sum(tf.subtract(circuit_out, -1))) grads = tape.gradient(mse, layer.trainable_weights) optimizer.apply_gradients(zip(grads, layer.trainable_weights)) self.assertAllClose(mse.numpy(), 0, atol=1e-3)
def test_simple_op_and_param_input(self, backend): """Test a simple operator and parameter input. Train a NN to put a qubit in the z+ or x+ states based on a classical binary input. """ bit = cirq.GridQubit(0, 0) symbols = sympy.symbols('x y z') ops = util.convert_to_tensor([[cirq.Z(bit)], [cirq.Z(bit)]]) n = tf.convert_to_tensor([[5000], [5000]], dtype=tf.int32) circuits = util.convert_to_tensor([ _gen_single_bit_rotation_problem( bit, symbols, True if backend == 'noisy' else False) ] * 2) data_in = np.array([[1], [0]]) data_out = np.array([[1], [1]]) data_inp = tf.keras.layers.Input(shape=(1), dtype=tf.dtypes.float32) op_inp = tf.keras.layers.Input(shape=(1, ), dtype=tf.dtypes.string) n_inp = tf.keras.layers.Input(shape=(1, ), dtype=tf.dtypes.int32) circuit_inp = tf.keras.Input(shape=(), dtype=tf.dtypes.string) dense_1 = tf.keras.layers.Dense(10)(data_inp) dense_2 = tf.keras.layers.Dense(3)(dense_1) circuit_output = sampled_expectation.SampledExpectation( backend=backend)(circuit_inp, symbol_names=symbols, symbol_values=dense_2, operators=op_inp, repetitions=n_inp) functional_model = tf.keras.Model( inputs=[circuit_inp, data_inp, op_inp, n_inp], outputs=[circuit_output]) functional_model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.05), loss=tf.keras.losses.mean_squared_error) history = functional_model.fit(x=[circuits, data_in, ops, n], y=data_out, batch_size=2, epochs=20) self.assertAllClose(history.history['loss'][-1], 0, atol=3)
def __init__( self, model_circuit, operators, *, repetitions=None, backend='noiseless', differentiator=None, initializer=tf.keras.initializers.RandomUniform(0, 2 * np.pi), regularizer=None, constraint=None, **kwargs, ): """Instantiate this layer. Create a layer that will output expectation values of the given operators when fed quantum data to it's input layer. This layer will accept one input tensor representing a quantum data source (these circuits must not contain any symbols) and append the model_circuit to them, execute them and then finally output the expectation values. 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: Optional Python `int` indicating how many samples to use when estimating expectation values. If `None` analytic expectation calculation is used. backend: Optional Backend to use to simulate states. Defaults to the noiseless TensorFlow simulator, however users may also specify a preconfigured cirq simulation object to use instead. If a cirq object is given it must inherit either `cirq.sim.simulator.SimulatesExpectationValues` if analytic expectations are desired or `cirq.Sampler` if sampled expectations are desired. differentiator: Optional `tfq.differentiator` object to specify how gradients of `model_circuit` should be calculated. initializer: Optional `tf.keras.initializer` object to specify how the symbols in `model_circuit` should be initialized when creating the managed variables. regularizer: Optional `tf.keras.regularizer` object applied to the managed variables parameterizing `model_circuit`. constraint: Optional `tf.keras.constraint` object applied to the managed variables parameterizing `model_circuit`. """ 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._model_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. self._analytic = False if repetitions is None: self._analytic = True if not self._analytic and not isinstance(repetitions, numbers.Integral): raise TypeError("repetitions must be a positive integer value." " Given: ".format(repetitions)) if not self._analytic and repetitions <= 0: raise ValueError("Repetitions must be greater than zero.") if not self._analytic: self._repetitions = tf.constant( [[repetitions for _ in range(len(operators))]], dtype=tf.dtypes.int32) # Set backend and differentiator. if backend == 'noisy': raise ValueError("noisy backend value is not supported in " "tfq.layers.PQC. Please use tfq.layers.NoisyPQC " "instead.") not_default = backend is not 'noiseless' not_default &= backend is not None # legacy backend=None support. if not isinstance( backend, cirq.Sampler) and repetitions is not None and not_default: raise TypeError("provided backend does not inherit cirq.Sampler " "and repetitions!=None. Please provide a backend " "that inherits cirq.Sampler or set " "repetitions=None.") if not isinstance(backend, cirq.sim.simulator.SimulatesExpectationValues ) and repetitions is None and not_default: raise TypeError( "provided backend does not inherit " "cirq.sim.simulator.SimulatesExpectationValues and " "repetitions=None. Please provide a backend that " "inherits " "cirq.sim.simulator.SimulatesExpectationValues.") if self._analytic: self._executor = expectation.Expectation( backend=backend, differentiator=differentiator) else: self._executor = sampled_expectation.SampledExpectation( backend=backend, differentiator=differentiator) self._append_layer = elementary.AddCircuit() # Set additional parameter controls. self.initializer = tf.keras.initializers.get(initializer) self.regularizer = tf.keras.regularizers.get(regularizer) self.constraint = tf.keras.constraints.get(constraint) # Weight creation is not placed in a Build function because the number # of weights is independent of the input shape. self.parameters = self.add_weight('parameters', shape=self._symbols.shape, initializer=self.initializer, regularizer=self.regularizer, constraint=self.constraint, dtype=tf.float32, trainable=True)
def test_static_cases(self): """Run inputs through in complex cases.""" bit = cirq.GridQubit(0, 0) symbol = sympy.Symbol('alpha') test_pstring = cirq.Z(bit) test_psum = cirq.PauliSum.from_pauli_strings([test_pstring]) symb_circuit = cirq.Circuit(cirq.H(bit)**symbol) reg_circuit = cirq.Circuit(cirq.H(bit)) # Passing a 2d operators input requires a 1d circuit input. sampled_expectation.SampledExpectation()( [reg_circuit, reg_circuit], operators=[[test_psum, test_psum], [test_psum, test_psum]], repetitions=1) # Passing 2d operators along with other inputs. sampled_expectation.SampledExpectation()( [symb_circuit, symb_circuit], symbol_names=[symbol], operators=[[test_psum, test_psum], [test_psum, test_psum]], repetitions=1) sampled_expectation.SampledExpectation()( [symb_circuit, symb_circuit], symbol_names=[symbol], symbol_values=[[0.5], [0.8]], operators=[[test_psum, test_psum], [test_psum, test_psum]], repetitions=1) # Ensure tiling up of circuits works as expected. sampled_expectation.SampledExpectation()(reg_circuit, operators=test_psum, repetitions=1) sampled_expectation.SampledExpectation()( reg_circuit, operators=[test_psum, test_psum], repetitions=1) # Ensure tiling up of symbol_values works as expected. sampled_expectation.SampledExpectation()(symb_circuit, symbol_names=[symbol], symbol_values=[[0.5], [0.8]], operators=test_psum, repetitions=1) sampled_expectation.SampledExpectation()(symb_circuit, symbol_names=[symbol], symbol_values=[[0.5]], operators=test_psum, repetitions=1) # Test multiple operators with integer valued repetition. sampled_expectation.SampledExpectation()( symb_circuit, symbol_names=[symbol], symbol_values=[[0.5]], operators=[-1.0 * cirq.Z(bit), cirq.X(bit) + 2.0 * cirq.Z(bit)], repetitions=1) sampled_expectation.SampledExpectation()( symb_circuit, symbol_names=[symbol], symbol_values=[[0.5]], operators=[-1.0 * cirq.Z(bit), cirq.X(bit) + 2.0 * cirq.Z(bit)], repetitions=[5, 1])
def __init__(self, model_circuit, operators, *, repetitions=None, backend=None, differentiator=None, **kwargs): """Instantiate this layer. Create a layer that will output 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: Optional Python `int` indicating how many samples to use when estimating expectation values. If `None` analytic expectation calculation is used. backend: Optional Backend to use to simulate states. Defaults to the native TensorFlow simulator (None), however users may also specify a preconfigured cirq simulation object to use instead. If a cirq object is given it must inherit `cirq.SimulatesFinalState` if `sampled_based` is True or it must inherit `cirq.Sampler` if `sample_based` is False. 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 reptitions. self._analytic = False if repetitions is None: self._analytic = True if not self._analytic and not isinstance(repetitions, numbers.Integral): raise TypeError("repetitions must be a positive integer value." " Given: ".format(repetitions)) if not self._analytic and repetitions <= 0: raise ValueError("Repetitions must be greater than zero.") if not self._analytic: self._repetitions = tf.constant( [[repetitions for _ in range(len(operators))]], dtype=tf.dtypes.int32) if not isinstance( backend, cirq.Sampler ) and repetitions is not None and backend is not None: raise TypeError("provided backend does not inherit cirq.Sampler " "and repetitions!=None. Please provide a backend " "that inherits cirq.Sampler or set " "repetitions=None.") if not isinstance(backend, cirq.SimulatesFinalState ) and repetitions is None and backend is not None: raise TypeError("provided backend does not inherit " "cirq.SimulatesFinalState and repetitions=None. " "Please provide a backend that inherits " "cirq.SimulatesFinalState.") # Ingest backend and differentiator. if self._analytic: self._layer = expectation.Expectation( backend=backend, differentiator=differentiator) else: self._layer = sampled_expectation.SampledExpectation( backend=backend, differentiator=differentiator) self._append_layer = elementary.AddCircuit()