quantum icon indicating copy to clipboard operation
quantum copied to clipboard

Preserving state of qubits when training

Open jdchen2 opened this issue 3 years ago • 6 comments

Hi, I am still fairly new to tfq and I have a question about training a quantum circuit. Currently, I have an expectation layer that measures the fidelity of an input and expected output qubit using a swap test. However, my circuit also involves helper qubits that interact with the input qubit. I would like to preserve the state of the helper qubits over each training iteration. (i.e. after training on the first data point, I would like to preserve the state of the helper qubits and use the state of the helper qubits in the next data point). Is something like this possible?

jdchen2 avatar Mar 24 '22 21:03 jdchen2

When you say you want to keep the state of the helper qubits do you mean the parameters or the exact wavefunction at the end of the execution? Maybe if you just give some more details into what exactly is being optimized, that could shed some light. Like at each iteration are the operations on the helper qubits the same or if their state is continued, do you only need the operations once then you just use that initial state for the rest of the iterations? It sort of sounds like you are trying to do mid circuit measurements (which I don't think is in TFQ or cirq, but don't quote me on that), but if you give a higher level overview, I could potentially give insight into a more feasible method of achieving the same goal.

Here are some pictures to clarify what I mean. This would be repeated operations but with different parameters (could easily be the same parameters as well): image

This would be only operations on the first iteration: image

lockwo avatar Mar 24 '22 23:03 lockwo

Hi,

Thanks for the reply! The first diagram is essentially what I am looking for (The operations on the helper qubits are different for each iteration) I wasn't able to find anything on whether mid-circuit measurements are possible in cirq/tfq so would you be able to share what you mean by an alternative method to achieve the same goal? Also, one concern I have with this design is that the number of input qubits scales linearly with the number of iterations which would result in extremely large simulations. Is there a way to "discard" input1 or reset input1 and then load input2 after the first measurement?

jdchen2 avatar Mar 28 '22 18:03 jdchen2

Mid circuit measurements (and qubit resets) are hard to do sometimes because they are can be non-unitary. I might do something like just keep track of your operations on the helper qubits and simply redo them when you need higher depth. Then you also only need one qubit for your inputs. So rather than doing ops on helpers then measuring then doing more ops then measure, you do ops then measure, create a new system then do the same ops, plus more then measure (to get the added depth). There are some redundant calculations, but I think it would work. See a trivial example of what I mean below. Here we have 3 different inputs, and 3 different depths with each operation being different at each depth for the helper qubits.

import tensorflow as tf
import tensorflow_quantum as tfq
import cirq
import sympy
import numpy as np

input_qubit = cirq.GridQubit(0, 0)
expected_output_qubit = cirq.GridQubit(0, 1)
swap_test_qubit = cirq.GridQubit(0, 2)
helper_qubits = [cirq.GridQubit(0, 3), cirq.GridQubit(0, 4)]

input_states = [cirq.Circuit(cirq.X(input_qubit)), cirq.Circuit(cirq.Y(input_qubit)), cirq.Circuit(cirq.Z(input_qubit))]

# I just picked these randomly
def layer(depth):
  c = cirq.Circuit()
  for i in range(depth):
    if depth == 1:
      c += cirq.Y(helper_qubits[0])
      c += cirq.X(helper_qubits[1])
    elif depth == 2:
      c += cirq.H(helper_qubits[0])
      c += cirq.Y(helper_qubits[1])
    elif depth == 3:
      c += cirq.S(helper_qubits[0])
      c += cirq.T(helper_qubits[0])
    c += cirq.CNOT(helper_qubits[0], expected_output_qubit)
    c += cirq.CNOT(helper_qubits[1], expected_output_qubit)
  return c

def swap_test(circuit):
  circuit += cirq.H(swap_test_qubit)
  circuit += cirq.ControlledGate(sub_gate=cirq.SWAP, num_controls=1).on(swap_test_qubit, input_qubit, expected_output_qubit)
  circuit += cirq.H(swap_test_qubit)
  return circuit

layers = [layer(i) for i in range(1, 4)]
circuits = [swap_test(input_states[i] + layers[i]) for i in range(len(layers))]
op = cirq.Z(swap_test_qubit)

expectation_layer = tfq.layers.Expectation()
results = expectation_layer(circuits, symbol_names=[], symbol_values=[[], [], []], operators=[op])
print(results)

lockwo avatar Mar 28 '22 18:03 lockwo

Thanks again for the help! I actually designed something similar, but the issue with this circuit is that it doesn't handle inputs the way I would like. Ideally I would like to "load" a new input at each layer. For example, if depth=2, I would like input_state[0] to be the input for layer 0, then reset the input qubit and then append input_state[1] to be the "input" for layer 1. Would there be a way to do this with cirq/tfq? As an alternative, rather than appending layers, I was wondering if it would be possible to load a state following the measurement/expectation layer (i.e. can I do something like process input 0, access the state of the helper qubits, load the state of the helper qubits for input 1, process input 1, and repeat?)

jdchen2 avatar Apr 05 '22 16:04 jdchen2

Well you can do that in cirq by simply using the reset op. In TFQ that is not a syntheziable op (you would get the following error if you tried Could not parse gate id: RST. This is likely because a cirq.Channel was used in an op that does not support them.) so that method can't be used. However, you could easily do the same thing by simply uncomputing the state of qubit (which should have minimal computational overhead since it is one qubit) back to |0> right when you would use the reset op (which is what reset does).

lockwo avatar Apr 05 '22 17:04 lockwo

Any updates on this or should it be closed?

lockwo avatar Aug 14 '22 23:08 lockwo