Notebook illustrating Qualtran -> QIR Compiler#658
Notebook illustrating Qualtran -> QIR Compiler#658skushnir123 wants to merge 6 commits intoquantumlib:mainfrom
Conversation
There was a problem hiding this comment.
Are the changes here mostly due to running the notebook? We typically clear output of all notebooks before committing to the repository.
There was a problem hiding this comment.
Yes, just from running stuff. I can revert these changes.
tanujkhattar
left a comment
There was a problem hiding this comment.
This is a great start! It'd be pretty cool if we can also hookup the QIR output of QFT with the microsoft resource estimator to produce resource estimation numbers. But we can also do that in a follow up PR
There was a problem hiding this comment.
Let's create a new folder qualtran/qir_interop/ and put all the code for QIR interop there.
| "outputs": [], | ||
| "source": [ | ||
| "import numpy as np\n", | ||
| "import pyqir\n", |
There was a problem hiding this comment.
We'll have to add pyqir to the requirements file and recompile dependencies using dev_tools/requirements/re-pip-compile.sh
| "@attrs.frozen\n", | ||
| "class Swap(Bloq):\n", | ||
| " n: int\n", | ||
| "\n", | ||
| " @property\n", | ||
| " def signature(self):\n", | ||
| " return Signature.build(x=self.n, y=self.n)\n", | ||
| "\n", | ||
| " def build_composite_bloq(\n", | ||
| " self, bb: BloqBuilder, *, x: SoquetT, y: SoquetT\n", | ||
| " ) -> Dict[str, SoquetT]:\n", | ||
| " xs = bb.split(x)\n", | ||
| " ys = bb.split(y)\n", | ||
| "\n", | ||
| " for i in range(self.n):\n", | ||
| " xs[i], ys[i] = bb.add(CNOT(), ctrl=xs[i], target=ys[i])\n", | ||
| " return {\n", | ||
| " 'x': bb.join(xs),\n", | ||
| " 'y': bb.join(ys),\n", | ||
| " }\n", |
There was a problem hiding this comment.
I think these three are good examples to use as test cases for the conversion code in the new bloq_to_qir_test.py file
| "def get_num_qubits_for_bloq(bloq: qualtran.Bloq):\n", | ||
| " num_qubits = 0\n", | ||
| " for register in bloq.signature.lefts():\n", | ||
| " shape = register.shape[0] if len(register.shape) != 0 else 1\n", | ||
| " num_qubits += register.bitsize*shape\n", | ||
| " return num_qubits\n", | ||
| "\n", | ||
| "def create_func_for_bloq(bloq: qualtran.Bloq, name, qubit_type, void_type, mod: pyqir.Module):\n", | ||
| " num_qubits = get_num_qubits_for_bloq(bloq)\n", | ||
| " return Function(\n", | ||
| " pyqir.FunctionType(void_type, [qubit_type]*num_qubits),\n", | ||
| " Linkage.EXTERNAL,\n", | ||
| " name,\n", | ||
| " mod\n", | ||
| " )\n", | ||
| "\n", | ||
| "def create_ir_map(bloq: qualtran.Bloq):\n", | ||
| " param_counter = 0\n", | ||
| " ir_map = {}\n", | ||
| " # Loop through all registers in signature\n", | ||
| " for register in bloq.signature.lefts():\n", | ||
| " shape = register.shape[0] if len(register.shape) != 0 else 1 # get the shape as an int (we are asumming its 1d for simplicity)\n", | ||
| " # map the (register_name, index_in_register) to the overall index\n", | ||
| " ir_map.update({(register.name, i*register.bitsize + j): param_counter + i*register.bitsize + j for i in range(shape) for j in range(register.bitsize)})\n", | ||
| " param_counter += register.bitsize * shape\n", | ||
| " return ir_map\n", |
There was a problem hiding this comment.
We can move all the utility functions to a new file bloq_to_qir.py and add tests using the example cases shown above.
| " ir_map = create_ir_map(bloq)\n", | ||
| " soq_map = {}\n", | ||
| "\n", | ||
| " # It seems like QFTTextBook and PhaseGradientUnitary can be decomposed even though supports_decompose_bloq returns false\n", |
There was a problem hiding this comment.
Seems like a bug in the GateWithRegisters base class. We should override this method for the GateWithRegister class to check whether the sub-bloq defines a cirq style decomposition and if yes, the return True from supports_decompose_bloq.
Can you open a separate issue to track this?
cc @mpharrigan
| " return bloq_func, ir_map\n", | ||
| "\n", | ||
| "\n", | ||
| "def convert_qualtran(bloq: qualtran.Bloq):\n", |
There was a problem hiding this comment.
| "def convert_qualtran(bloq: qualtran.Bloq):\n", | |
| "def bloq_to_qir(bloq: qualtran.Bloq):\n", |
| " irs = generate_irs_from_soquet(soquet)\n", | ||
| " return [ir_map[ir] for ir in irs]\n", | ||
| "\n", | ||
| "def compile_bloq(bloq: qualtran.Bloq, qubit_type, void_type, module, context, builder, func_dict=dict()):\n", |
There was a problem hiding this comment.
Please add docstrings and return type annotations to all the helper functions.
| " }\n", | ||
| "\n", | ||
| "@attrs.frozen\n", | ||
| "class ExampleNonTrivialShapeBloq(Bloq):\n", |
There was a problem hiding this comment.
Another example to add (maybe as a followup) would be ExampleNonTrivialSideBloq which has LEFT and RIGHT directional registers instead of all registers being THRU registers.
I think in terms of QIR, a LEFT register implies de-allocation happens inside that function and a RIGHT register corresponds to allocations happening inside that function. If the concepts don't translate directly (eg: because QIR assumes you take care of qubit allocations and deallocations globally and have a sea of globally allocated qubits) then I think both LEFT & RIGHT registers would be treated as inputs to the corresponding the QIR function (we do this when supporting cirq style decomposition where decompose_from_registers() method gets both LEFT & RIGHT registers as preallocated ancilla qubits.
Tl;Dr - Converting bloqs with directional registers should be tracked as a future improvement in the issue
Part of #622