Summary
The idea is to allow common simple 1 and 2 qubit circuits to be built with a string passed to Qunitary
. This would allow a for an elegant interaction to build complicated circuits from simple building blocks. For example:
Qinit(2)+Qcycle(mapping=Qunitary(f"CRX(x0)^01,CRZ(x1)^10)"))
![simple_cycle](https://private-user-images.githubusercontent.com/51360109/239740241-81928b0d-0bd3-46f2-b3a7-4467a89dc1f8.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjE2MDI5MTYsIm5iZiI6MTcyMTYwMjYxNiwicGF0aCI6Ii81MTM2MDEwOS8yMzk3NDAyNDEtODE5MjhiMGQtMGJkMy00NmYyLWIzYTctNDQ2N2E4OWRjMWY4LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA3MjElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNzIxVDIyNTY1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTExOTU1Y2ZiYWYwN2FlNjI1NjJhZjAyZjUyMDEyMjRjYjE0MTJiNzNlMDcxZjc0MjhmMjExNTcyY2U0N2FmNDkmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.v6xk-BycanTqpZKjbrl7VQhSxBOSmO8SeNgPiegA59w)
Where currently this is achieved as follows:
def anz1(bits, symbols=None, circuit=None):
# Assume bits are strings and in the correct QASM format
q0, q1 = QuantumRegister(1, bits[0]), QuantumRegister(1, bits[1])
circuit.crx(symbols[0], q0, q1)
circuit.crz(symbols[1], q1, q0)
return circuit
u = Qunitary(anz1, 2, 2)
hierq = Qinit(5) + Qcycle(mapping=u)
Qunitary
can receive any function, and we want to allow it to receive a string that specifies a simple circuit. Emphasis on simple, the reason for this feature is to enable building small circuits quickly without needing to create a function, we don't need to cater for all possible use cases, just the commonly used gates. The string format I have in mind is:
"{gate_string}(parameters)^{bits},{gate_string}(parameters)^{bits},...,{gate_string}(parameters)^{bits}"
Where gate_string is one of: h,x,y,z,cx,cy,cz,rx,ry,rz,crx,cry,crz,s,tof,fred and always in either upper case or lower case. Then each gates {parameters} are specified in brackets (x1,x2,...,xn) separated with commas and n<10 (to avoid dis ambiguity and keep it simple. Then ^ specifies bits (the qubit indices that the gate acts upon). We should only allow up to 0-9 since most of the time only 2 qubit circuits are going to be created. The arity and number of symbols can then be determined by the distinct number of bits used and the total number of parameters specified, this should happen when Qunitary is initialised.
The conversion needs to happen when the Qhierarchy object is called (depending on the backend it executes get_circuit_qiskit for example). We'll need some mapping for the gate strings and the corresponding gate name for the backend used (we can stick with qiskit for now). Then some function needs to be constructed, I envisage something along the lines of:
def get_circuit_from_string(qunitary):
def circuit_fn(bits, symbols=None, circuit=None):
arity=qunitary.arity
qubits = [QuantumRegister(1, bits[i]) for i in range(arity)]
s_idx = 0
for gate_name, sub_bits, n_subsymbols in get_gateinfo_from_string(qunitary.circuit_string, arity, n_symbols):
gate = getattr(circuit, gate_name)
gate(*symbols[s_idx:n_subsymbols], *qubits.get(sub_bits))
return circuit
return circuit_fn
But that's just a guess, in particular I don't know how to handle the sub_bits.
For this issue, you can pick any framework (qiskit, pennylane, cirq) and implement the functionality for one of them. Feel free to come up with a better design, the above is just an idea for how to allow simple string circuits to Qunitary.