diff --git a/.gitignore b/.gitignore index 3b1f476..713bc86 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ env/ build/ develop-eggs/ dist/ +docs/modules.rst +docs/qiskit_qir.rst downloads/ eggs/ .eggs/ @@ -105,3 +107,5 @@ ENV/ # IDE settings .vscode/ .idea/ + +.DS_Store diff --git a/Makefile b/Makefile index 8191c52..b218a7e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,9 @@ -.PHONY: clean clean-build clean-pyc clean-test coverage dist docs help install lint lint/flake8 lint/black +## +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +## + +.PHONY: clean clean-build clean-pyc clean-test coverage deps dist docs help install lint lint/flake8 lint/black release test test-all test-release venv .DEFAULT_GOAL := help define BROWSER_PYSCRIPT @@ -23,8 +28,17 @@ export PRINT_HELP_PYSCRIPT BROWSER := python -c "$$BROWSER_PYSCRIPT" +VENV = .venv +VENV_PYTHON = $(VENV)/bin/python +SYSTEM_PYTHON = $(or $(shell which python3), $(shell which python)) +PYTHON = $(or $(wildcard $(VENV_PYTHON)), $(SYSTEM_PYTHON)) + +$(VENV_PYTHON): + rm -rf $(VENV) + $(SYSTEM_PYTHON) -m venv --upgrade-deps $(VENV) + help: - @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) + $(PYTHON) -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts @@ -47,25 +61,21 @@ clean-test: ## remove test and coverage artifacts rm -fr htmlcov/ rm -fr .pytest_cache -lint/flake8: ## check style with flake8 - flake8 src tests -lint/black: ## check style with black - black --check src tests - -lint: lint/flake8 lint/black ## check style - -test: ## run tests quickly with the default Python - pytest - -test-all: ## run tests on every Python version with tox - tox - coverage: ## check code coverage quickly with the default Python - coverage run --source src -m pytest - coverage report -m - coverage html + $(PYTHON) -m coverage run --source src -m pytest + $(PYTHON) -m coverage report -m + $(PYTHON) -m coverage html $(BROWSER) htmlcov/index.html +deps: ## Install development dependencies + $(PYTHON) -m pip install -r requirements_dev.txt + $(PYTHON) setup.py develop + +dist: clean ## builds source and wheel package + $(PYTHON) setup.py sdist + $(PYTHON) setup.py bdist_wheel + ls -l dist + docs: ## generate Sphinx HTML documentation, including API docs rm -f docs/qiskit_qir.rst rm -f docs/modules.rst @@ -74,19 +84,32 @@ docs: ## generate Sphinx HTML documentation, including API docs $(MAKE) -C docs html $(BROWSER) docs/_build/html/index.html -servedocs: docs ## compile the docs watching for changes - watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . +install: clean ## install the package to the active Python's site-packages + $(PYTHON) setup.py install + +lint/flake8: ## check style with flake8 + $(PYTHON) -m flake8 src tests + +lint/black: ## check style with black + $(PYTHON) -m black --check src tests + +lint: lint/flake8 lint/black ## check style release: dist ## package and upload a release - twine upload dist/* + $(PYTHON) -m twine upload dist/* + +servedocs: docs ## compile the docs watching for changes + $(PYTHON) -m watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . + +test: ## run tests quickly with the default Python + $(PYTHON) -m pytest + +test-all: ## run tests on every Python version with tox + $(PYTHON) -m tox test-release: dist ## package and upload a release twine upload --repository testpypi dist/* -dist: clean ## builds source and wheel package - python setup.py sdist - python setup.py bdist_wheel - ls -l dist - -install: clean ## install the package to the active Python's site-packages - python setup.py install +venv: $(VENV_PYTHON) ## Creates the python virtual environment + $(VENV_PYTHON) --version + $(VENV_PYTHON) -m pip --version diff --git a/requirements_dev.txt b/requirements_dev.txt index 27b20ac..031b11e 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,11 +1,12 @@ -wheel>=0.41 +wheel>=0.42 watchdog==3.0.0 flake8==6.1.0 -tox==4.11.3 -coverage==7.3.2 -Sphinx==1.8.5 +tox==4.12.1 +coverage==7.4.1 +Sphinx==7.1.2 +jinja2==3.1.3 twine==4.0.2 -pytest==7.4.3 -black==23.11.0 +pytest==7.4.4 +black==24.1.1 pyqir==0.10.0 -qiskit-terra>=0.19.2,<1.0 \ No newline at end of file +qiskit>=1.0.0 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 32a3592..a78e4e0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = qiskit-qir -version = 0.4.0 +version = 0.5.0rc1 author = Microsoft author_email = que-contacts@microsoft.com description = Qiskit to QIR translator @@ -30,8 +30,7 @@ package_dir = packages = find: python_requires = >=3.8 install_requires = - qiskit>=0.34.2,<1.0 - qiskit-terra>=0.19.2,<1.0 + qiskit>=1.0.0,<2.0 pyqir>=0.10.0,<0.11.0 [options.extras_require] diff --git a/src/qiskit_qir/__init__.py b/src/qiskit_qir/__init__.py index f30143d..57d3bbd 100644 --- a/src/qiskit_qir/__init__.py +++ b/src/qiskit_qir/__init__.py @@ -4,6 +4,6 @@ ## __author__ = """Microsoft Corporation""" __email__ = "que-contacts@microsoft.com" -__version__ = "0.4.0" +__version__ = "0.5.0rc1" from qiskit_qir.translate import to_qir_module diff --git a/src/qiskit_qir/capability.py b/src/qiskit_qir/capability.py index 1ae0083..14bfde5 100644 --- a/src/qiskit_qir/capability.py +++ b/src/qiskit_qir/capability.py @@ -4,8 +4,8 @@ ## from enum import Flag, auto import os -from typing import List, Union -from qiskit import ClassicalRegister +from typing import Dict, List, Union +from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit.circuit import Qubit, Clbit from qiskit.circuit.instruction import Instruction @@ -20,18 +20,73 @@ class Capability(Flag): class CapabilityError(Exception): """Base class for profile validation exceptions""" - pass + def _get_bit_labels( + self, + circuit: QuantumCircuit, + ) -> Dict[Union[Qubit, Clbit], str]: + register_names: Dict[str, Union[QuantumRegister, ClassicalRegister]] = {} + for registers in (circuit.qregs, circuit.cregs): + for register in registers: + register_names[register.name] = register + bit_labels: Dict[Union[Qubit, Clbit], str] = { + bit: "%s[%d]" % (name, idx) + for name, register in register_names.items() + for (idx, bit) in enumerate(register) + } + return bit_labels + + def _get_instruction_string( + self, + bit_labels: Dict[Union[Qubit, Clbit], str], + instruction: Instruction, + qargs: List[Qubit], + cargs: List[Clbit], + ): + gate_params = ",".join(["param(%s)" % bit_labels[c] for c in cargs]) + qubit_params = ",".join(["%s" % bit_labels[q] for q in qargs]) + instruction_name = instruction.name + if instruction.condition is not None: + # condition should be a + # - tuple (ClassicalRegister, int) + # - tuple (Clbit, bool) + # - tuple (Clbit, int) + if isinstance(instruction.condition[0], Clbit): + bit: Clbit = instruction.condition[0] + value: Union[int, bool] = instruction.condition[1] + instruction_name = "if(%s[%d] == %s) %s" % ( + bit._register.name, + bit._index, + value, + instruction_name, + ) + else: + register: ClassicalRegister = instruction.condition[0] + value: int = instruction.condition[1] + instruction_name = "if(%s == %d) %s" % ( + register._name, + value, + instruction_name, + ) + + if gate_params: + return f"{instruction_name}({gate_params}) {qubit_params}" + else: + return f"{instruction_name} {qubit_params}" class ConditionalBranchingOnResultError(CapabilityError): def __init__( self, + circuit: QuantumCircuit, instruction: Instruction, qargs: List[Qubit], cargs: List[Clbit], profile: str, ): - instruction_string = _get_instruction_string(instruction, qargs, cargs) + bit_labels: Dict[Union[Qubit, Clbit], str] = self._get_bit_labels(circuit) + instruction_string = self._get_instruction_string( + bit_labels, instruction, qargs, cargs + ) self.instruction_string = instruction_string self.msg_suffix = "Support for branching based on measurement requires Capability.CONDITIONAL_BRANCHING_ON_RESULT" self.msg = f"Attempted to branch on register value.{os.linesep}Instruction: {instruction_string}{os.linesep}{self.msg_suffix}" @@ -45,12 +100,16 @@ def __init__( class QubitUseAfterMeasurementError(CapabilityError): def __init__( self, + circuit: QuantumCircuit, instruction: Instruction, qargs: List[Qubit], cargs: List[Clbit], profile: str, ): - instruction_string = _get_instruction_string(instruction, qargs, cargs) + bit_labels: Dict[Union[Qubit, Clbit], str] = self._get_bit_labels(circuit) + instruction_string = self._get_instruction_string( + bit_labels, instruction, qargs, cargs + ) self.instruction_string = instruction_string self.msg_suffix = ( "Support for qubit reuse requires Capability.QUBIT_USE_AFTER_MEASUREMENT" @@ -61,40 +120,3 @@ def __init__( self.qargs = qargs self.cargs = cargs self.profile = profile - - -def _get_instruction_string( - instruction: Instruction, qargs: List[Qubit], cargs: List[Clbit] -): - gate_params = ",".join( - ["param(%s[%i])" % (c.register.name, c.index) for c in cargs] - ) - qubit_params = ",".join(["%s[%i]" % (q.register.name, q.index) for q in qargs]) - instruction_name = instruction.name - if instruction.condition is not None: - # condition should be a - # - tuple (ClassicalRegister, int) - # - tuple (Clbit, bool) - # - tuple (Clbit, int) - if isinstance(instruction.condition[0], Clbit): - bit: Clbit = instruction.condition[0] - value: Union[int, bool] = instruction.condition[1] - instruction_name = "if(%s[%d] == %s) %s" % ( - bit._register.name, - bit._index, - value, - instruction_name, - ) - else: - register: ClassicalRegister = instruction.condition[0] - value: int = instruction.condition[1] - instruction_name = "if(%s == %d) %s" % ( - register._name, - value, - instruction_name, - ) - - if gate_params: - return f"{instruction_name}({gate_params}) {qubit_params}" - else: - return f"{instruction_name} {qubit_params}" diff --git a/src/qiskit_qir/elements.py b/src/qiskit_qir/elements.py index 2104936..9c2c32c 100644 --- a/src/qiskit_qir/elements.py +++ b/src/qiskit_qir/elements.py @@ -41,6 +41,7 @@ def accept(self, visitor): class QiskitModule: def __init__( self, + circuit: QuantumCircuit, name: str, module: Module, num_qubits: int, @@ -48,6 +49,7 @@ def __init__( reg_sizes: List[int], elements: List[_QuantumCircuitElement], ): + self._circuit = circuit self._name = name self._module = module self._elements = elements @@ -55,6 +57,10 @@ def __init__( self._num_clbits = num_clbits self.reg_sizes = reg_sizes + @property + def circuit(self) -> QuantumCircuit: + return self._circuit + @property def name(self) -> str: return self._name @@ -90,6 +96,7 @@ def from_quantum_circuit( if module is None: module = Module(Context(), circuit.name) return cls( + circuit=circuit, name=circuit.name, module=module, num_qubits=circuit.num_qubits, diff --git a/src/qiskit_qir/visitor.py b/src/qiskit_qir/visitor.py index 796f9bf..a9db11f 100644 --- a/src/qiskit_qir/visitor.py +++ b/src/qiskit_qir/visitor.py @@ -106,6 +106,7 @@ def visit_instruction(self, instruction): class BasicQisVisitor(QuantumCircuitElementVisitor): def __init__(self, profile: str = "AdaptiveExecution", **kwargs): self._module = None + self._qiskitModule: QiskitModule | None = None self._builder = None self._entry_point = None self._qubit_labels = {} @@ -121,6 +122,7 @@ def visit_qiskit_module(self, module: QiskitModule): f"Visiting Qiskit module '{module.name}' ({module.num_qubits}, {module.num_clbits})" ) self._module = module.module + self._qiskitModule = module context = self._module.context entry = entry_point( self._module, module.name, module.num_qubits, module.num_clbits @@ -221,7 +223,7 @@ def visit_instruction( instruction.condition is not None ) and not self._capabilities & Capability.CONDITIONAL_BRANCHING_ON_RESULT: raise ConditionalBranchingOnResultError( - instruction, qargs, cargs, self._profile + self._qiskitModule.circuit, instruction, qargs, cargs, self._profile ) labels = ", ".join([str(l) for l in qlabels + clabels]) @@ -305,7 +307,11 @@ def __branch(): if instruction.name in _SUPPORTED_INSTRUCTIONS: if any(map(self._measured_qubits.get, map(qubit_id, qubits))): raise QubitUseAfterMeasurementError( - instruction, qargs, cargs, self._profile + self._qiskitModule.circuit, + instruction, + qargs, + cargs, + self._profile, ) if "barrier" == instruction.name: if self._emit_barrier_calls: