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]) 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 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) cirq_exps = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, batch_pauli_sums, cirq.DensityMatrixSimulator() if noisy else cirq.Simulator()) tol = 0.5 self.assertAllClose(cirq_exps, op_exps, atol=tol, rtol=tol)
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.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]) 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 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) cirq_exps = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, batch_pauli_sums, cirq.DensityMatrixSimulator()) self.assertAllClose(cirq_exps, op_exps, atol=0.35, rtol=0.35)
def test_analytical_expectation(self, op_and_sim, n_qubits, symbol_names, max_paulisum_length): """Compute expectations using cirq and tfq.""" op = op_and_sim[0] sim = op_and_sim[1] 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]) pauli_sums = util.random_pauli_sums(qubits, max_paulisum_length, BATCH_SIZE) op_expectations = op( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[psum] for psum in pauli_sums])) cirq_expectations = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, [[x] for x in pauli_sums], sim) self.assertAllClose(op_expectations.numpy().flatten(), cirq_expectations.flatten(), rtol=1e-5, atol=1e-5)
def test_analytical_expectation_empty(self, op_and_sim, n_qubits, symbol_names, max_paulisum_length): """Test empty circuits for analytical expectation using cirq and tfq.""" op = op_and_sim[0] sim = op_and_sim[1] qubits = cirq.GridQubit.rect(1, n_qubits) circuit_batch = [cirq.Circuit() for _ in range(BATCH_SIZE)] resolver_batch = [cirq.ParamResolver({}) for _ in range(BATCH_SIZE)] symbol_values_array = np.array([[0.0 for _ in symbol_names] for _ in resolver_batch]) pauli_sums = util.random_pauli_sums(qubits, max_paulisum_length, BATCH_SIZE) op_expectations = op( util.convert_to_tensor(circuit_batch), symbol_names, symbol_values_array, util.convert_to_tensor([[psum] for psum in pauli_sums])) cirq_expectations = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, [[x] for x in pauli_sums], sim) self.assertAllClose(op_expectations.numpy().flatten(), cirq_expectations.flatten(), rtol=1e-5, atol=1e-5)
def test_no_circuit(self, sim): """Test functions with no circuits and empty arrays.""" # (1) Test expectation results = batch_util.batch_calculate_expectation([], [], [[]], sim) self.assertDTypeEqual(results, np.float32) self.assertEqual(np.zeros(shape=(0, 0)).shape, results.shape) # (2) Test sampled_expectation results = batch_util.batch_calculate_sampled_expectation([], [], [[]], [[]], sim) self.assertDTypeEqual(results, np.float32) self.assertEqual(np.zeros(shape=(0, 0)).shape, results.shape) # (3) Test state results = batch_util.batch_calculate_state([], [], sim) self.assertDTypeEqual(results, np.complex64) if isinstance(sim, cirq.Simulator): self.assertEqual(np.zeros(shape=(0, 0)).shape, results.shape) else: self.assertEqual(np.zeros(shape=(0, 0, 0)).shape, results.shape) # (4) Test sampling results = batch_util.batch_sample([], [], [], sim) self.assertDTypeEqual(results, np.int8) self.assertEqual(np.zeros(shape=(0, 0, 0)).shape, results.shape)
def test_empty_circuits(self, sim): """Test functions with empty circuits.""" # Common preparation resolver_batch = [cirq.ParamResolver({}) for _ in range(BATCH_SIZE)] circuit_batch = [cirq.Circuit() for _ in range(BATCH_SIZE)] qubits = cirq.GridQubit.rect(1, N_QUBITS) ops = util.random_pauli_sums(qubits, PAULI_LENGTH, BATCH_SIZE) n_samples = [[1000] for _ in range(len(ops))] # If there is no op on a qubit, the expectation answer is -2.0 true_expectation = (-2.0, ) # (1) Test expectation results = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, [[x] for x in ops], sim) for _, _, result, _ in zip(circuit_batch, resolver_batch, results, ops): self.assertAllClose(true_expectation, result, rtol=1e-5, atol=1e-5) self.assertDTypeEqual(results, np.float32) # (2) Test sampled_expectation results = batch_util.batch_calculate_sampled_expectation( circuit_batch, resolver_batch, [[x] for x in ops], n_samples, sim) for _, _, result, _ in zip(circuit_batch, resolver_batch, results, ops): self.assertAllClose(true_expectation, result, rtol=1.0, atol=1e-1) self.assertDTypeEqual(results, np.float32) # (3) Test state results = batch_util.batch_calculate_state(circuit_batch, resolver_batch, sim) for circuit, resolver, result in zip(circuit_batch, resolver_batch, results): r = _pad_state(sim, sim.simulate(circuit, resolver), 0) self.assertAllClose(r, result, rtol=1e-5, atol=1e-5) self.assertDTypeEqual(results, np.complex64) # (4) Test sampling n_samples = 2000 * (2**N_QUBITS) results = batch_util.batch_sample(circuit_batch, resolver_batch, n_samples, sim) for circuit, resolver, a in zip(circuit_batch, resolver_batch, results): state = sim.simulate(circuit, resolver) r = _sample_helper(sim, state, len(circuit.all_qubits()), n_samples) self.assertAllClose(r, a, atol=1e-5) self.assertDTypeEqual(results, np.int32)
def _cirq_simple_finite_difference(circuit_batch, resolvers, symbol_names, op_batch, grid_spacing=0.0001): """A simple finite difference code that calculates the gradient of a batch of circuits using cirq.""" simulator = cirq.sim.Simulator() init_vals = batch_util.batch_calculate_expectation(circuit_batch, resolvers, op_batch, simulator) grad_circuits = [] grad_resolvers = [] grad_pauli_sums = [] for this_program, this_pauli_sums, this_resolver in \ zip(circuit_batch, op_batch, resolvers): for symbol in symbol_names: perturbed_resolver = copy.deepcopy(this_resolver) perturbed_resolver.param_dict[symbol] += grid_spacing grad_circuits.append(this_program) grad_pauli_sums.append(this_pauli_sums) grad_resolvers.append(perturbed_resolver) # shape: [n_programs * len(symbol_names), n_pauli_sums] results = np.array( batch_util.batch_calculate_expectation(circuits=grad_circuits, param_resolvers=grad_resolvers, ops=grad_pauli_sums, simulator=simulator)) # shape: [n_pauli_sums, n_programs, len(symbol_names)] gradient_generator = results.transpose().reshape( (len(op_batch[0]), len(circuit_batch), len(symbol_names))) # shape: [n_pauli_sums, n_programs, len(symbol_names)] forward_pass_vals = np.transpose( np.vstack([np.expand_dims(init_vals, axis=0)] * len(symbol_names)), (2, 1, 0)) return np.sum(1 / grid_spacing * (gradient_generator - forward_pass_vals), axis=0)
def test_analytical_expectation_no_circuits(self, op_and_sim): """Test no circuits for states using cirq and tfq.""" op = op_and_sim[0] sim = op_and_sim[1] circuit_batch = tf.raw_ops.Empty(shape=(0,), dtype=tf.string) empty_params = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.float32) empty_ops = tf.raw_ops.Empty(shape=(0, 0), dtype=tf.string) op_exp = op(circuit_batch, [], empty_params, empty_ops).numpy() cirq_exp = batch_util.batch_calculate_expectation([], [], [[]], sim) self.assertEqual(op_exp.shape, cirq_exp.shape)
def test_batch_expectation(self, sim): """Test expectation.""" qubits = cirq.GridQubit.rect(1, N_QUBITS) circuit_batch, resolver_batch = _get_mixed_batch( qubits + [cirq.GridQubit(9, 9)], SYMBOLS, BATCH_SIZE) ops = util.random_pauli_sums(qubits, PAULI_LENGTH, BATCH_SIZE) results = batch_util.batch_calculate_expectation( circuit_batch, resolver_batch, [[x] for x in ops], sim) for circuit, resolver, result, op in zip(circuit_batch, resolver_batch, results, ops): r = _expectation_helper(sim, circuit, resolver, op) self.assertAllClose(r, result, rtol=1e-5, atol=1e-5) self.assertDTypeEqual(results, np.float32)
def cirq_analytical_expectation(programs, symbol_names, symbol_values, pauli_sums): """Calculate the expectation value of circuits wrt some operator(s). Calculate the expectation value for all the `cirq.PauliSum`s in `pauli_sums` on each `cirq.Circuit` in `programs`. Each circuit will have the values in `symbol_values` resolved into the symbols in the circuit (with the ordering defined by `symbol_names`). ```python symbol_names = ['a', 'b', 'c'] programs = tfq.convert_to_tensor( [cirq.Circuit(H(q0) ** sympy.Symbol('a'), X(q1) ** sympy.Symbol('b'), Y(q2) ** sympy.Symbol('c'))] ) symbol_values = [[3,2,1]] pauli_sums = tfq.convert_to_tensor( [1.5 * cirq.Z(q0) * cirq.Z(q1)] ) cirq_analytical_expectation( programs, symbol_names, sybmol_values, pauli_sums) ``` Would place the values of 3 into the Symbol labeled 'a', 2 into the symbol labeled 'b' and 1 into the symbol labeled 'c'. Then it would calculate the ZZ expectation on this circuit. 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`. 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. 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). """ _input_check_helper(programs, symbol_names, symbol_values) if not (pauli_sums.dtype == tf.dtypes.string): raise TypeError('pauli_sums tensor must be of type string.') if not (pauli_sums.shape[0] == programs.shape[0]): raise TypeError('pauli_sums tensor must have the same batch shape ' 'as programs tensor.') programs, resolvers = _batch_deserialize_helper(programs, symbol_names, symbol_values) sum_inputs = [] for sub_list in pauli_sums.numpy(): to_append = [] for x in sub_list: obj = pauli_sum_pb2.PauliSum() obj.ParseFromString(x) to_append.append(serializer.deserialize_paulisum(obj)) sum_inputs.append(to_append) expectations = batch_util.batch_calculate_expectation( programs, resolvers, sum_inputs, simulator) return expectations