<jupyter_start><jupyter_text>Basic Parameterized Quantum Circuit<jupyter_code>a, b = sp.symbols('a b')
# Create two qubits
q0, q1 = cirq.GridQubit.rect(1, 2)

# Create circuit on these qubits using params `a` and `b`
circuit = cirq.Circuit(
    cirq.rx(a).on(q0),
    cirq.ry(b).on(q1), cirq.CNOT(control=q0, target=q1)
)

SVGCircuit(circuit)<jupyter_output><empty_output><jupyter_text>Circuit Evaluation<jupyter_code># Calculate a state vector with a=0.5, b=-0.5
resolver = cirq.ParamResolver({a: 0.5, b: -0.5})

output_state_vector = cirq.Simulator().simulate(circuit, resolver).final_state
output_state_vector<jupyter_output><empty_output><jupyter_text>Pauli $\hat Z_0$ Measurement on State Vector<jupyter_code>z0 = cirq.Z(q0)

qubit_map = {q0: 0, q1: 1}

z0.expectation_from_wavefunction(output_state_vector, qubit_map).real<jupyter_output><empty_output><jupyter_text>Pauli $\frac{1}{2} \hat Z_0 + \hat X_1$ Measurement on State Vector<jupyter_code>z0x1 = 0.5 * z0 + cirq.X(q1)

z0x1.expectation_from_wavefunction(output_state_vector, qubit_map).real
# Rank 1 tensor containing 1 circuit
circuit_tensor = tfq.convert_to_tensor([circuit])

print(circuit_tensor.shape)
print(circuit_tensor.dtype)
# Rank 1 tensor containing 2 Pauli operators
pauli_tensor = tfq.convert_to_tensor([z0, z0x1])
pauli_tensor.shape
# batch of values to sub for `a` and `b` parameters
batch_vals = np.array(np.random.uniform(0, 2 * np.pi, (5, 2)), dtype=np.float32)

cirq_results = []
cirq_simulator = cirq.Simulator()

for val in batch_vals:
    resolver = cirq.ParamResolver({a: val[0], b: val[1]})
    final_state = cirq_simulator.simulate(circuit, resolver).final_state
    cirq_results.append(
        [z0.expectation_from_wavefunction(final_state, {
            q0: 0,
            q1: 1
        }).real])
    
print('cirq batch results: \n {}'.format(np.array(cirq_results)))<jupyter_output>cirq batch results: 
 [[ 0.92059928]
 [ 0.80442852]
 [-0.53074777]
 [ 0.44015628]
 [ 0.28704041]]<jupyter_text>Hybrid Neural Network  Controlled Circuit<jupyter_code># Parameters that the classical Nn will feed values into
control_params = sp.symbols('theta_1 theta_2 theta_3')

# Create parameterized circuit
qubit = cirq.GridQubit(0, 0)

model_circuit = cirq.Circuit(
    cirq.rz(control_params[0])(qubit),
    cirq.ry(control_params[1])(qubit),
    cirq.rx(control_params[2])(qubit)
)

SVGCircuit(model_circuit)<jupyter_output><empty_output><jupyter_text>Controller<jupyter_code># Classical neural network layers
controller = tf.keras.Sequential([
    tf.keras.layers.Dense(10, activation='elu'),
    tf.keras.layers.Dense(3)
])

controller(tf.constant([[0.0],[1.0]])).numpy()<jupyter_output><empty_output><jupyter_text>Controller-Circuit Connection<jupyter_code># Simulated miscalibration that model will learn to correct
circuits_input = tf.keras.Input(shape=(),
                                # The circuit-tensor has dtype `tf.string` 
                                dtype=tf.string,
                                name='circuits_input')

# Set qubits' state to `0` or `1` based on command
commands_input = tf.keras.Input(shape=(1,),
                                dtype=tf.dtypes.float32,
                                name='commands_input')<jupyter_output><empty_output><jupyter_text>Apply operations to above inputs to define the computation:<jupyter_code>dense_2 = controller(commands_input)

# TFQ layer for classically controlled circuits
expectation_layer = tfq.layers.ControlledPQC(model_circuit,
                                             # Observe Z
                                             operators = cirq.Z(qubit))
expectation = expectation_layer([circuits_input, dense_2])<jupyter_output><empty_output><jupyter_text>Package computation as Keras model:<jupyter_code>model = tf.keras.Model(inputs=[circuits_input, commands_input],
                       outputs=expectation)
tf.keras.utils.plot_model(model, show_shapes=True, dpi=70)
# Command input values to the classical NN
commands = np.array([[0], [1]], dtype=np.float32)

# Desired Z expectation value at output of quantum circuit
expected_outputs = np.array([[1], [-1]], dtype=np.float32)
# Input circuit that defines random miscalibration the model will learn to correct
random_rotations = np.random.uniform(0, 2 * np.pi, 3)
noisy_preparation = cirq.Circuit(
  cirq.rx(random_rotations[0])(qubit),
  cirq.ry(random_rotations[1])(qubit),
  cirq.rz(random_rotations[2])(qubit)
)
datapoint_circuits = tfq.convert_to_tensor([
  noisy_preparation
] * 2)  # Make two copied of this circuit

# datapoint_circuits.shape # 2 copies of circuit; 1 copy for each datapoint

# Dry run of tfq model
model([datapoint_circuits, commands]).numpy()
# Run training process to adjust output values towards `expected_outputs`
optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)
loss = tf.keras.losses.MeanSquaredError()
model.compile(optimizer=optimizer, loss=loss)
history = model.fit(x=[datapoint_circuits, commands],
                    y=expected_outputs,
                    epochs=30,
                    verbose=0)

# Plot training information
plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()<jupyter_output><empty_output><jupyter_text>Verify Outputs Use the trained model to correct the qubit calibration errors.<jupyter_code>def check_error(command_values, desired_values):
  """Based on the value in `command_value` see how well you could prepare
  the full circuit to have `desired_value` when taking expectation w.r.t. Z."""
  params_to_prepare_output = controller(command_values).numpy()
  full_circuit = noisy_preparation + model_circuit

  # Test how well you can prepare a state to get the expectation
  # value in `desired_values`
  for index in [0, 1]:
    state = cirq_simulator.simulate(
        full_circuit,
        {s:v for (s,v) in zip(control_params, params_to_prepare_output[index])}
    ).final_state
    expectation = z0.expectation_from_wavefunction(state, {qubit: 0}).real
    print(f'For a desired output (expectation) of {desired_values[index]} with'
          f' noisy preparation, the controller\nnetwork found the following '
          f'values for theta: {params_to_prepare_output[index]}\nWhich gives an'
          f' actual expectation of: {expectation}\n')


check_error(commands, expected_outputs)<jupyter_output>For a desired output (expectation) of [1.] with noisy preparation, the controller
network found the following values for theta: [ 0.75017124  0.4912489  -1.800404  ]
Which gives an actual expectation of: 0.9485020637512207

For a desired output (expectation) of [-1.] with noisy preparation, the controller
network found the following values for theta: [ 1.7805516   1.2925966  -0.12267798]
Which gives an actual expectation of: -0.9495996832847595<jupyter_text>Eigenstates of Different Operators<jupyter_code># Define inputs
commands_input = tf.keras.layers.Input(shape=(1),
                                       dtype=tf.dtypes.float32,
                                       name='commands_input')
circuits_input = tf.keras.Input(shape=(),
                                # The circuit-tensor has dtype `tf.string` 
                                dtype=tf.dtypes.string,
                                name='circuits_input')
operators_input = tf.keras.Input(shape=(1,),
                                 dtype=tf.dtypes.string,
                                 name='operators_input')

# Define classical NN
controller = tf.keras.Sequential([
    tf.keras.layers.Dense(10, activation='elu'),
    tf.keras.layers.Dense(3)
])
dense_2 = controller(commands_input)

# append model circuit onto datapoint circuit tensor manually, since PQC or ControlledPQC aren't used
full_circuit = tfq.layers.AddCircuit()(circuits_input, append=model_circuit)
expectation_output = tfq.layers.Expectation()(full_circuit,
                                              symbol_names=control_params,
                                              symbol_values=dense_2,
                                              operators=operators_input)

# Contruct Keras model
two_axis_control_model = tf.keras.Model(
    inputs=[circuits_input, commands_input, operators_input],
    outputs=[expectation_output])

# Operators to measure for each command
operator_data = tfq.convert_to_tensor([[cirq.X(qubit)], [cirq.Z(qubit)]])

# Command input values to the classical NN
commands = np.array([[0], [1]], dtype=np.float32)

# Desired expectation value at output of quantum circuit
expected_outputs = np.array([[1], [-1]], dtype=np.float32)<jupyter_output><empty_output><jupyter_text>Training<jupyter_code>optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)
loss = tf.keras.losses.MeanSquaredError()

two_axis_control_model.compile(optimizer=optimizer, loss=loss)

history = two_axis_control_model.fit(
    x=[datapoint_circuits, commands, operator_data],
    y=expected_outputs,
    epochs=30,
    verbose=1)

# Plot training results
plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()
controller.predict(np.array([0,1]))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11041146455325154, 'fraction_numerical': 0.02844425143849745, 'mean_word_length': 3.8179916317991633, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 47, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '396193', 'license': 'Apache-1.1', 'n_tokens_mistral': 3199, 'n_tokens_neox': 2971, 'n_words': 645, 'path': 'tutorials/hello-many-worlds.ipynb', 'repo_name': 'coopbri/tfq', 'size': '9211'}