yhtang / FunFact

Tensor decomposition with arbitrary expressions: inner, outer, elementwise operators; nonlinear transformations; and more.
Other
57 stars 4 forks source link

funfact quantum module #235

Closed campsd closed 2 years ago

campsd commented 2 years ago

A module for simple quantum circuits and gates.

Example usage:

import funfact.quantum as ffq
circ = ffq.Circuit(3)
circ.append(ffq.TwoQubitUnitary(0))
circ.append(ffq.TwoQubitUnitary(1))
circ.append(ffq.CX(0, 2))
tsrex = circ.to_tensor()
fac = ff.Factorization.from_tsrex(tsrex)
fac()

This can help us optimize parametrized circuits, and eventually integrate with qiskit #221

There's two outstanding issues:

  1. the dtype should be handled in a consistent manner: it should always be a complex data type?
  2. the offset in the circuits are not yet handled correctly when circuits are added to circuits.
campsd commented 2 years ago

Another open issue is how we can map an optimized tensor expression back to a circuit object. This could as simple as iterating over the factors of the optimized model and assigning the result to the tensors in the circuit object. What do you think @yhtang ?

yhtang commented 2 years ago

Nice work overall!

yhtang commented 2 years ago

There's two outstanding issues:

  1. the dtype should be handled in a consistent manner: it should always be a complex data type?

Agree. For this particular application area, I guess we can start with the two most common options complex64 and complex128. Not sure if 16-bit floats is useful but it could the subject of a future project :grin:

  1. the offset in the circuits are not yet handled correctly when circuits are added to circuits.

Could you please clarify a little bit? Does offset refer to the position of the qubits?

yhtang commented 2 years ago

Another open issue is how we can map an optimized tensor expression back to a circuit object. This could as simple as iterating over the factors of the optimized model and assigning the result to the tensors in the circuit object. What do you think @yhtang ?

This can be broken down into two sub-problems that we can solve independently:

  1. Is there a way for us to uniquely label each circuit & its corresponding abstract tensor?
  2. Is there a way to enumerate/access the gates in a circuit so that we can update them with concrete tensors?
campsd commented 2 years ago

There's two outstanding issues:

  1. the dtype should be handled in a consistent manner: it should always be a complex data type?

Agree. For this particular application area, I guess we can start with the two most common options complex64 and complex128. Not sure if 16-bit floats is useful but it could the subject of a future project 😁

I was unsure how we enforce this, but I guess using something like fac = ff.Factorization.from_tsrex(circ.to_tsrex(), dtype=ab.complex128) would work?

  1. the offset in the circuits are not yet handled correctly when circuits are added to circuits.

Could you please clarify a little bit? Does offset refer to the position of the qubits?

This optional argument specifies if the qubits of the circuit don't range from 0 to nbqubits-1 but from offset to nbqubits-1+offset. It is ignored if a circuit only consists of gates, but if you add a circuit to another circuit, you can control the placement of the subcircuit with this. For example:

circ2 = ffq.Circuit(2, offset=0)
circ2.append(ffq.OneQubitUnitary(0))
circ2.append(ffq.CX(1, 0))
circ1 = ffq.Circuit(1, offset=1)
circ1.append(ffq.PauliY(0))
circ2.append(circ1) # circ1 now acts on qubit 1 of circ2
circ2.to_tsrex()
campsd commented 2 years ago

Another open issue is how we can map an optimized tensor expression back to a circuit object. This could as simple as iterating over the factors of the optimized model and assigning the result to the tensors in the circuit object. What do you think @yhtang ?

This can be broken down into two sub-problems that we can solve independently:

  1. Is there a way for us to uniquely label each circuit & its corresponding abstract tensor?
  2. Is there a way to enumerate/access the gates in a circuit so that we can update them with concrete tensors?

This makes sense. Shall we handle this in a follow up PR?

campsd commented 2 years ago

Also for future reference: the to_tsrex function in the Circuit class is very naive. It places gates one by one and blows all of them up to dimension 2**n, this can be significantly improved to reduce the cost. I suggest that we move forward with the naive implementation for now and optimize it later.

yhtang commented 2 years ago

This makes sense. Shall we handle this in a follow up PR?

Sure. I'm pretty sure this is going to take more than one PRs to accomplish :grin:

yhtang commented 2 years ago

I was unsure how we enforce this, but I guess using something like fac = ff.Factorization.from_tsrex(circ.to_tsrex(), dtype=ab.complex128) would work?

Actually this sounds like a better option. So basically we factor out the representation of a quantum circuit from its numerical realization.

yhtang commented 2 years ago

This optional argument specifies if the qubits of the circuit don't range from 0 to nbqubits-1 but from offset to nbqubits-1+offset. It is ignored if a circuit only consists of gates, but if you add a circuit to another circuit, you can control the placement of the subcircuit with this. For example:

circ2 = ffq.Circuit(2, offset=0)
circ2.append(ffq.OneQubitUnitary(0))
circ2.append(ffq.CX(1, 0))
circ1 = ffq.Circuit(1, offset=1)
circ1.append(ffq.PauliY(0))
circ2.append(circ1) # circ1 now acts on qubit 1 of circ2
circ2.to_tsrex()

I see. Would it be better to specify the placement of a circuit at the time when it gets inserted into another one? In this way, we can define a circuit once and then reuse it multiple times at different places in a bigger circuit.

This feels a bit like the index renaming mechanism that we implemented to the tensor expressions. For example, we can define a circuit using a set of 'nomial' qubits and then map them onto actual ones when placing them into a bigger one.

yhtang commented 2 years ago

This feels a bit like the index renaming mechanism that we implemented to the tensor expressions. For example, we can define a circuit using a set of 'nomial' qubits and then map them onto actual ones when placing them into a bigger one.

I wonder how qiskit or a hardware description language handles this scenario.😎

campsd commented 2 years ago

This feels a bit like the index renaming mechanism that we implemented to the tensor expressions. For example, we can define a circuit using a set of 'nomial' qubits and then map them onto actual ones when placing them into a bigger one.

I wonder how qiskit or a hardware description language handles this scenario.😎

For how qiskit handles this scenario, see: https://qiskit.org/documentation/tutorials/circuits_advanced/01_advanced_circuits.html#Composition

you can attach a subcircuit to certain qubits of the main circuit in the append function.

campsd commented 2 years ago

Further updates based on our discussion before.

Current usage examples:

circ = ffq.Circuit(3)
G1 = ffq.TwoQubitUnitary()
circ.append(G1 @ (0, 1))
circ.append(G1, at=(1, 2))
circ = ffq.Circuit(2)
CNOT = ffq.CX()
circ.append(CNOT @ (1, 0)) # first = control, second = target