diff --git a/.dep-versions b/.dep-versions index 88489adecb..9d8acd920b 100644 --- a/.dep-versions +++ b/.dep-versions @@ -3,4 +3,7 @@ jax=0.4.23 mhlo=4611968a5f6818e6bdfb82217b9e836e0400bba9 llvm=cd9a641613eddf25d4b25eaa96b2c393d401d42c enzyme=1beb98b51442d50652eaa3ffb9574f4720d611f1 + +# Always remove custom PL/LQ versions before release. pennylane=f638bc53a09724a83d9d58964bf37bfd438e6aa3 +lightning=0.35.0-dev21 diff --git a/.github/workflows/build-wheel-linux-x86_64.yaml b/.github/workflows/build-wheel-linux-x86_64.yaml index 5f0ad2485e..b0cbd4e924 100644 --- a/.github/workflows/build-wheel-linux-x86_64.yaml +++ b/.github/workflows/build-wheel-linux-x86_64.yaml @@ -292,7 +292,7 @@ jobs: -DPYTHON_EXECUTABLE=$(which python${{ matrix.python_version }}) \ -Dpybind11_DIR=$(python${{ matrix.python_version }} -c "import pybind11; print(pybind11.get_cmake_dir())") \ -DENABLE_LIGHTNING_KOKKOS=ON \ - -DLIGHTNING_GIT_TAG="2716864521c539429bd382b92151a918d1a076ce" \ + -DLIGHTNING_GIT_TAG="03e465d96a665a2d1bcea2f1e6d79321e9d1c3fe" \ -DKokkos_ENABLE_SERIAL=ON \ -DKokkos_ENABLE_OPENMP=ON \ -DENABLE_WARNINGS=OFF \ @@ -376,15 +376,15 @@ jobs: run: | python${{ matrix.python_version }} -m pip install pytest pytest-xdist - - name: Install Catalyst - run: | - python${{ matrix.python_version }} -m pip install $GITHUB_WORKSPACE/dist/*.whl - - name: Install PennyLane Plugins run: | python${{ matrix.python_version }} -m pip install PennyLane-Lightning-Kokkos python${{ matrix.python_version }} -m pip install amazon-braket-pennylane-plugin "boto3==1.26" + - name: Install Catalyst + run: | + python${{ matrix.python_version }} -m pip install $GITHUB_WORKSPACE/dist/*.whl --extra-index-url https://test.pypi.org/simple + - name: Run Python Pytest Tests run: | python${{ matrix.python_version }} -m pytest $GITHUB_WORKSPACE/frontend/test/pytest -n auto diff --git a/.github/workflows/build-wheel-macos-arm64.yaml b/.github/workflows/build-wheel-macos-arm64.yaml index 571a7d704c..645ea22340 100644 --- a/.github/workflows/build-wheel-macos-arm64.yaml +++ b/.github/workflows/build-wheel-macos-arm64.yaml @@ -289,7 +289,7 @@ jobs: -DPYTHON_EXECUTABLE=$(which python${{ matrix.python_version }}) \ -Dpybind11_DIR=$(python${{ matrix.python_version }} -c "import pybind11; print(pybind11.get_cmake_dir())") \ -DENABLE_LIGHTNING_KOKKOS=ON \ - -DLIGHTNING_GIT_TAG="2716864521c539429bd382b92151a918d1a076ce" \ + -DLIGHTNING_GIT_TAG="03e465d96a665a2d1bcea2f1e6d79321e9d1c3fe" \ -DKokkos_ENABLE_SERIAL=ON \ -DKokkos_ENABLE_OPENMP=ON \ -DKokkos_ENABLE_COMPLEX_ALIGN=OFF \ @@ -385,15 +385,15 @@ jobs: run: | python${{ matrix.python_version }} -m pip install pytest pytest-xdist - - name: Install Catalyst - run: | - python${{ matrix.python_version }} -m pip install $GITHUB_WORKSPACE/dist/*.whl - - name: Install PennyLane Plugins run: | python${{ matrix.python_version }} -m pip install PennyLane-Lightning-Kokkos python${{ matrix.python_version }} -m pip install amazon-braket-pennylane-plugin + - name: Install Catalyst + run: | + python${{ matrix.python_version }} -m pip install $GITHUB_WORKSPACE/dist/*.whl --extra-index-url https://test.pypi.org/simple + - name: Run Python Pytest Tests run: | python${{ matrix.python_version }} -m pytest -v $GITHUB_WORKSPACE/frontend/test/pytest -n auto diff --git a/.github/workflows/build-wheel-macos-x86_64.yaml b/.github/workflows/build-wheel-macos-x86_64.yaml index c2a6214150..5f04623c4c 100644 --- a/.github/workflows/build-wheel-macos-x86_64.yaml +++ b/.github/workflows/build-wheel-macos-x86_64.yaml @@ -261,7 +261,7 @@ jobs: -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=$GITHUB_WORKSPACE/runtime-build/lib \ -DPYTHON_EXECUTABLE=$(which python${{ matrix.python_version }}) \ -Dpybind11_DIR=$(python${{ matrix.python_version }} -c "import pybind11; print(pybind11.get_cmake_dir())") \ - -DLIGHTNING_GIT_TAG="2716864521c539429bd382b92151a918d1a076ce" \ + -DLIGHTNING_GIT_TAG="03e465d96a665a2d1bcea2f1e6d79321e9d1c3fe" \ -DENABLE_LIGHTNING_KOKKOS=ON \ -DKokkos_ENABLE_SERIAL=ON \ -DKokkos_ENABLE_OPENMP=OFF \ @@ -355,15 +355,15 @@ jobs: run: | python${{ matrix.python_version }} -m pip install pytest pytest-xdist - - name: Install Catalyst - run: | - python${{ matrix.python_version }} -m pip install $GITHUB_WORKSPACE/dist/*.whl - - name: Install PennyLane Plugins run: | python${{ matrix.python_version }} -m pip install PennyLane-Lightning-Kokkos python${{ matrix.python_version }} -m pip install amazon-braket-pennylane-plugin + - name: Install Catalyst + run: | + python${{ matrix.python_version }} -m pip install $GITHUB_WORKSPACE/dist/*.whl --extra-index-url https://test.pypi.org/simple + - name: Run Python Pytest Tests run: | python${{ matrix.python_version }} -m pytest $GITHUB_WORKSPACE/frontend/test/pytest -n auto diff --git a/.github/workflows/check-catalyst.yaml b/.github/workflows/check-catalyst.yaml index 54cfb11d23..78eed99305 100644 --- a/.github/workflows/check-catalyst.yaml +++ b/.github/workflows/check-catalyst.yaml @@ -378,14 +378,14 @@ jobs: - name: Install Deps run: | sudo apt-get update - sudo apt-get install -y python3 python3-pip libomp-dev libasan6 + sudo apt-get install -y python3 python3-pip libomp-dev libasan6 make python3 --version | grep ${{ needs.constants.outputs.primary_python_version }} python3 -m pip install -r requirements.txt # cuda-quantum is added manually here. # It can't be in requirements.txt as that will break # macOS requirements.txt python3 -m pip install cuda-quantum - python3 -m pip install . + make frontend - name: Get Cached LLVM Build id: cache-llvm-build @@ -447,13 +447,17 @@ jobs: - name: Checkout Catalyst repo uses: actions/checkout@v3 + - name: Install lightning.kokkos used in Python tests + run: | + pip install PennyLane-Lightning-Kokkos + - name: Install Deps run: | sudo apt-get update - sudo apt-get install -y python3 python3-pip libomp-dev libasan6 + sudo apt-get install -y python3 python3-pip libomp-dev libasan6 make python3 --version | grep ${{ needs.constants.outputs.primary_python_version }} python3 -m pip install -r requirements.txt - python3 -m pip install . + make frontend - name: Get Cached LLVM Build id: cache-llvm-build @@ -481,10 +485,6 @@ jobs: echo "RUNTIME_LIB_DIR=$(pwd)/runtime-build/lib" >> $GITHUB_ENV echo "MLIR_LIB_DIR=$(pwd)/llvm-build/lib" >> $GITHUB_ENV - - name: Install lightning.kokkos used in Python tests - run: | - pip install PennyLane-Lightning-Kokkos - - name: Run Python Pytest Tests (backend=lightning.kokkos) run: | make pytest TEST_BACKEND="lightning.kokkos" @@ -504,10 +504,10 @@ jobs: - name: Install Deps run: | sudo apt-get update - sudo apt-get install -y python3 python3-pip libomp-dev libasan6 + sudo apt-get install -y python3 python3-pip libomp-dev libasan6 make python3 --version | grep ${{ needs.constants.outputs.primary_python_version }} python3 -m pip install -r requirements.txt - python3 -m pip install . + make frontend - name: Get Cached LLVM Build id: cache-llvm-build diff --git a/.github/workflows/check-pl-compat.yaml b/.github/workflows/check-pl-compat.yaml index ff4fb277e7..6ee7f7455a 100644 --- a/.github/workflows/check-pl-compat.yaml +++ b/.github/workflows/check-pl-compat.yaml @@ -101,13 +101,7 @@ jobs: DIALECTS_BUILD_DIR="$(pwd)/quantum-build" \ ENABLE_LLD=ON \ make dialects - if [ ${{ inputs.pennylane }} = "stable" ]; then - pip install --upgrade . - else - # TODO(@erick-xanadu): Remove after release. See issue - # https://github.com/PennyLaneAI/catalyst/issues/494 - pl_version="==0.35.0.dev0" pip install --upgrade . - fi + make frontend - name: Build Catalyst Runtime (latest) if: ${{ inputs.lightning == 'latest' }} @@ -128,6 +122,7 @@ jobs: C_COMPILER=$(which gcc }}) \ CXX_COMPILER=$(which g++ }}) \ RT_BUILD_DIR="$(pwd)/runtime-build" \ + LIGHTNING_GIT_TAG_VALUE=latest_release \ ENABLE_LIGHTNING_KOKKOS=ON \ ENABLE_OPENQASM=ON \ make runtime diff --git a/Makefile b/Makefile index 6cadd97b7a..d64e0f1278 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ all: runtime mlir frontend .PHONY: frontend frontend: @echo "install Catalyst Frontend" - $(PYTHON) -m pip install -e . + $(PYTHON) -m pip install -e . --extra-index-url https://test.pypi.org/simple rm -r frontend/PennyLane_Catalyst.egg-info .PHONY: mlir llvm mhlo enzyme dialects runtime @@ -202,7 +202,7 @@ coverage-frontend: @echo "Generating coverage report for the frontend" $(ASAN_COMMAND) $(PYTHON) -m pytest frontend/test/pytest $(PARALLELIZE) --cov=catalyst --tb=native --cov-report=$(COVERAGE_REPORT) ifeq ($(TEST_BRAKET), NONE) - $(ASAN_COMMAND) $(PYTHON) -m pytest frontend/test/async_tests --tb=native --backend=$(TEST_BACKEND) --tb=native + $(ASAN_COMMAND) $(PYTHON) -m pytest frontend/test/async_tests --tb=native --backend=$(TEST_BACKEND) --tb=native endif coverage-runtime: diff --git a/doc/changelog.md b/doc/changelog.md index 9b0186ec11..0e10f4d9a4 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -71,6 +71,7 @@ This release contains contributions from (in alphabetical order): * Catalyst now supports QJIT compatible `catalyst.vmap` of hybrid programs. `catalyst.vmap` offers the vectorization mapping backed by `catalyst.for_loop`. [(#497)](https://github.com/PennyLaneAI/catalyst/pull/497) + [(#569)](https://github.com/PennyLaneAI/catalyst/pull/569) For example, @@ -102,6 +103,8 @@ This release contains contributions from (in alphabetical order): >>> catalyst.qjit(catalyst.grad(f))(x) [1. 0. 1. 0.] ``` +* Add support for `GlobalPhase` gate in the runtime. + [(#563)](https://github.com/PennyLaneAI/catalyst/pull/563) * Catalyst no longer relies on a TensorFlow installation for its AutoGraph functionality. Instead, the standalone `diastatic-malt` package is used and automatically installed as a dependency. diff --git a/doc/requirements.txt b/doc/requirements.txt index d424b5bef2..1bc9f7fc10 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -25,3 +25,7 @@ m2r2 mistune==0.8.4 sphinxext-opengraph==0.9.0 matplotlib==3.8.0 + +# Pre-install development lightning wheels +--extra-index-url https://test.pypi.org/simple/ +pennylane-lightning==0.35.0-dev21 diff --git a/frontend/catalyst/jax_primitives.py b/frontend/catalyst/jax_primitives.py index c0897f3f89..c2236fd58a 100644 --- a/frontend/catalyst/jax_primitives.py +++ b/frontend/catalyst/jax_primitives.py @@ -46,6 +46,7 @@ DeviceReleaseOp, ExpvalOp, ExtractOp, + GlobalPhaseOp, HamiltonianOp, HermitianOp, InsertOp, @@ -174,6 +175,8 @@ def _obs_lowering(aval): qdealloc_p.multiple_results = True qextract_p = core.Primitive("qextract") qinsert_p = core.Primitive("qinsert") +gphase_p = core.Primitive("gphase") +gphase_p.multiple_results = True qinst_p = core.Primitive("qinst") qinst_p.multiple_results = True qunitary_p = core.Primitive("qunitary") @@ -677,6 +680,81 @@ def _qinsert_lowering( return InsertOp(qreg_type, qreg_old, qubit, idx=qubit_idx).results +# +# gphase +# +@gphase_p.def_abstract_eval +def _gphase_abstract_eval( + *qubits_or_params, op=None, qubits_len: int = 0, params_len: int = 0, ctrl_len: int = 0 +): + # The signature here is: (using * to denote zero or more) + # qubits*, params*, ctrl_qubits*, ctrl_values* + qubits = qubits_or_params[:qubits_len] + ctrl_qubits = qubits_or_params[-2 * ctrl_len : -ctrl_len] + all_qubits = qubits + ctrl_qubits + for idx in range(qubits_len + ctrl_len): + qubit = all_qubits[idx] + assert isinstance(qubit, AbstractQbit) + return (AbstractQbit(),) * (qubits_len + ctrl_len) + + +@qinst_p.def_impl +def _gphase_abstract_eval( + *qubits_or_params, op=None, qubits_len: int = 0, params_len: int = 0, ctrl_len: int = 0 +): + """Not implemented""" + raise NotImplementedError() + + +def _gphase_lowering( + jax_ctx: mlir.LoweringRuleContext, + *qubits_or_params, + op=None, + qubits_len: int = 0, + params_len: int = 0, + ctrl_len: int = 0, +): + ctx = jax_ctx.module_context.context + ctx.allow_unregistered_dialects = True + + qubits = qubits_or_params[:qubits_len] + params = qubits_or_params[qubits_len : qubits_len + params_len] + ctrl_qubits = qubits_or_params[qubits_len + params_len : qubits_len + params_len + ctrl_len] + ctrl_values = qubits_or_params[qubits_len + params_len + ctrl_len :] + + float_params = [] + assert 1 == len(params), "Only one param in GlobalPhase" + for p in params: + if ir.RankedTensorType.isinstance(p.type) and ir.RankedTensorType(p.type).shape == []: + baseType = ir.RankedTensorType(p.type).element_type + + if not ir.F64Type.isinstance(baseType): + baseType = ir.F64Type.get() + resultTensorType = ir.RankedTensorType.get((), baseType) + p = ConvertOp(resultTensorType, p).results + + p = TensorExtractOp(baseType, p, []).result + + assert ir.F64Type.isinstance( + p.type + ), "Only scalar double parameters are allowed for quantum gates!" + + float_params.append(p) + + ctrl_values_i1 = [] + for v in ctrl_values: + p = TensorExtractOp(ir.IntegerType.get_signless(1), v, []).result + ctrl_values_i1.append(p) + + GlobalPhaseOp( + params=float_params[0], + out_ctrl_qubits=[qubit.type for qubit in ctrl_qubits], + in_ctrl_qubits=ctrl_qubits, + in_ctrl_values=ctrl_values_i1, + ) + return qubits + ctrl_qubits + + # # qinst # @@ -1576,6 +1654,7 @@ def _adjoint_lowering( mlir.register_lowering(qextract_p, _qextract_lowering) mlir.register_lowering(qinsert_p, _qinsert_lowering) mlir.register_lowering(qinst_p, _qinst_lowering) +mlir.register_lowering(gphase_p, _gphase_lowering) mlir.register_lowering(qunitary_p, _qunitary_lowering) mlir.register_lowering(qmeasure_p, _qmeasure_lowering) mlir.register_lowering(compbasis_p, _compbasis_lowering) diff --git a/frontend/catalyst/jax_tracer.py b/frontend/catalyst/jax_tracer.py index ecd4076c10..b93c53123a 100644 --- a/frontend/catalyst/jax_tracer.py +++ b/frontend/catalyst/jax_tracer.py @@ -57,6 +57,7 @@ counts_p, expval_p, func_p, + gphase_p, hamiltonian_p, hermitian_p, mlir_fn_cache, @@ -116,7 +117,7 @@ def _eval_jaxpr(*args): # Take care when adding primitives to this set in order to avoid introducing a quadratic number of # edges to the jaxpr equation graph in ``sort_eqns()``. Each equation with a primitive in this set # is constrained to occur before all subsequent equations in the quantum operations trace. -FORCED_ORDER_PRIMITIVES = {qdevice_p} +FORCED_ORDER_PRIMITIVES = {qdevice_p, gphase_p} PAULI_NAMED_MAP = { "I": "Identity", @@ -435,6 +436,17 @@ def _bind_native_controlled_op(qrp, op, controlled_wires, controlled_values): ) qrp.insert(op.wires, qubits2[: len(qubits)]) qrp.insert(controlled_wires, qubits2[len(qubits) :]) + elif isinstance(op, qml.GlobalPhase): + qubits = qrp.extract(op.wires) + controlled_qubits = qrp.extract(controlled_wires) + qubits2 = gphase_p.bind( + *[*qubits, *op.parameters, *controlled_qubits, *controlled_values], + qubits_len=len(qubits), + params_len=len(op.parameters), + ctrl_len=len(controlled_qubits), + ) + qrp.insert(op.wires, qubits2[: len(qubits)]) + qrp.insert(controlled_wires, qubits2[len(qubits) :]) else: qubits = qrp.extract(op.wires) controlled_qubits = qrp.extract(controlled_wires) diff --git a/frontend/catalyst/pennylane_extensions.py b/frontend/catalyst/pennylane_extensions.py index eb0e509727..ea9d3028f5 100644 --- a/frontend/catalyst/pennylane_extensions.py +++ b/frontend/catalyst/pennylane_extensions.py @@ -2229,10 +2229,7 @@ def postcircuit(y, x, z): in the output. ``out_axes`` is subject to the same modes as well. """ - # Dispatch to jax.vmap when it is called outside qjit. - if not EvaluationContext.is_tracing(): - return jax.vmap(fn, in_axes, out_axes) - + # Check the validity of in_axes and out_axes if not all(isinstance(l, int) for l in tree_leaves(in_axes)): raise ValueError( "Invalid 'in_axes'; it must be an int or a tuple of PyTrees with integer leaves, " @@ -2248,6 +2245,10 @@ def postcircuit(y, x, z): def batched_fn(*args, **kwargs): """Vectorization wrapper around the hybrid program using catalyst.for_loop""" + # Dispatch to jax.vmap when it is called outside qjit. + if not EvaluationContext.is_tracing(): + return jax.vmap(fn, in_axes, out_axes)(*args, **kwargs) + args_flat, args_tree = tree_flatten(args) in_axes_flat, _ = tree_flatten(in_axes, is_leaf=lambda x: x is None) diff --git a/frontend/catalyst/qjit_device.py b/frontend/catalyst/qjit_device.py index 75286c4677..e6ab209c29 100644 --- a/frontend/catalyst/qjit_device.py +++ b/frontend/catalyst/qjit_device.py @@ -77,6 +77,7 @@ class QJITDevice(qml.QubitDevice): "QubitUnitary", "ISWAP", "PSWAP", + "GlobalPhase", } @staticmethod @@ -98,11 +99,6 @@ def _check_adjoint(config): @staticmethod def _check_quantum_control(config): - # TODO: Remove this special case when we depend on the version of PL-lightning that includes - # the quantum_control = True. - # https://github.com/PennyLaneAI/catalyst/pull/559 - if config["device"]["name"] == "lightning.qubit": - return True return config["compilation"]["quantum_control"] @staticmethod diff --git a/frontend/test/pytest/test_global_phase.py b/frontend/test/pytest/test_global_phase.py new file mode 100644 index 0000000000..46e084660f --- /dev/null +++ b/frontend/test/pytest/test_global_phase.py @@ -0,0 +1,76 @@ +# Copyright 2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for Global Phase""" + +import numpy as np +import pennylane as qml +import pytest + +from catalyst import cond, qjit + + +def test_global_phase(backend): + """Test vanilla global phase""" + dev = qml.device(backend, wires=1) + + @qml.qnode(dev) + def qnn(): + qml.RX(np.pi / 4, wires=[0]) + qml.GlobalPhase(np.pi / 4) + return qml.state() + + expected = qnn() + observed = qjit(qnn)() + assert np.allclose(expected, observed) + + +@pytest.mark.parametrize("inp", [True, False]) +def test_global_phase_in_region(backend, inp): + """Test global phase in region""" + dev = qml.device(backend, wires=1) + + @qml.qnode(dev) + def qnn(c): + qml.RX(np.pi / 4, wires=[0]) + + @cond(c) + def cir(): + qml.GlobalPhase(np.pi / 4) + + cir() + return qml.state() + + expected = qnn(inp) + observed = qjit(qnn)(inp) + assert np.allclose(expected, observed) + + +def test_global_phase_control(backend): + """Test global phase controlled""" + + if backend == "lightning.kokkos": + pytest.skip("control phase is unsupported in kokkos or at least its toml file.") + + dev = qml.device(backend, wires=2) + + @qml.qnode(dev) + def qnn(): + qml.RX(np.pi / 4, wires=[0]) + qml.ctrl(qml.GlobalPhase(np.pi / 4), control=[0]) + return qml.state() + + expected = qnn() + observed = qjit(qnn)() + assert np.allclose(expected, observed) diff --git a/frontend/test/pytest/test_vmap.py b/frontend/test/pytest/test_vmap.py index 00315bc12e..0d5b31c4b0 100644 --- a/frontend/test/pytest/test_vmap.py +++ b/frontend/test/pytest/test_vmap.py @@ -128,6 +128,30 @@ def circuit(x): assert jnp.allclose(result[1], expected) assert jnp.allclose(result[2], expected) + def test_vmap_circuit_inside_without_jax_dispatch(self, backend): + """Test catalyst.vmap of a hybrid workflow inside QJIT.""" + + @qml.qnode(qml.device(backend, wires=1)) + def circuit(x): + qml.RX(jnp.pi * x[0], wires=0) + qml.RY(x[1] ** 2, wires=0) + qml.RX(x[1] * x[2], wires=0) + return qml.expval(qml.PauliZ(0)) + + x = jnp.array( + [ + [0.1, 0.2, 0.3], + [0.4, 0.5, 0.6], + [0.7, 0.8, 0.9], + ] + ) + + result0 = qjit(vmap(circuit))(x) + result1 = qjit(vmap(circuit, in_axes=(0,)))(x) + expected = jnp.array([0.93005586, 0.00498127, -0.88789978]) + assert jnp.allclose(result0, expected) + assert jnp.allclose(result1, expected) + def test_vmap_circuit_in_axes_int(self, backend): """Test catalyst.vmap of a hybrid workflow inside QJIT with `in_axes:int`.""" diff --git a/mlir/include/Quantum/IR/QuantumInterfaces.td b/mlir/include/Quantum/IR/QuantumInterfaces.td index 867372d6f4..e9f6756e84 100644 --- a/mlir/include/Quantum/IR/QuantumInterfaces.td +++ b/mlir/include/Quantum/IR/QuantumInterfaces.td @@ -104,9 +104,6 @@ def QuantumGate : OpInterface<"QuantumGate"> { let verify = [{ auto gate = mlir::cast($_op); - if (gate.getQubitOperands().size() < 1) - return $_op->emitOpError("must have at least 1 qubit"); - if (gate.getCtrlValueOperands().size() != gate.getCtrlQubitOperands().size()) return $_op->emitError() << "number of controlling qubits in input (" << diff --git a/mlir/include/Quantum/IR/QuantumOps.td b/mlir/include/Quantum/IR/QuantumOps.td index 101adaae3d..05798f3149 100644 --- a/mlir/include/Quantum/IR/QuantumOps.td +++ b/mlir/include/Quantum/IR/QuantumOps.td @@ -272,6 +272,73 @@ def CustomOp : Gate_Op<"custom", [DifferentiableGate, }]; } +def GlobalPhaseOp : Quantum_Op<"gphase", [DifferentiableGate, AttrSizedOperandSegments]> { + let summary = "Global Phase."; + let description = [{ + }]; + + let arguments = (ins + F64:$params, + OptionalAttr:$adjoint, + Variadic:$in_ctrl_qubits, + Variadic:$in_ctrl_values + ); + + let results = (outs + Variadic:$out_ctrl_qubits + ); + + let assemblyFormat = [{ + `(` $params `)` attr-dict ( `ctrls` `(` $in_ctrl_qubits^ `)` )? ( `ctrlvals` `(` $in_ctrl_values^ `)` )? `:` (`ctrls` type($out_ctrl_qubits)^ )? + }]; + + code extraBaseClassDeclaration = [{ + mlir::ValueRange getAllParams() { + return getODSOperands(getParamOperandIdx()); + } + + std::vector getQubitOperands() { + std::vector values; + values.insert(values.end(), getInCtrlQubits().begin(), + getInCtrlQubits().end()); + return values; + } + + std::vector getQubitResults() { + std::vector values; + values.insert(values.end(), getOutCtrlQubits().begin(), + getOutCtrlQubits().end()); + return values; + } + + bool getAdjointFlag() { + return getAdjoint().has_value() ? getAdjoint().value() : false; + } + void setAdjointFlag(bool adjoint) { + setAdjoint(adjoint); + }; + + mlir::ValueRange getCtrlValueOperands() { + return getInCtrlValues(); + } + mlir::ValueRange getNonCtrlQubitOperands() { + return mlir::ValueRange(); + } + mlir::ValueRange getCtrlQubitOperands() { + return getInCtrlQubits(); + } + mlir::ValueRange getNonCtrlQubitResults() { + return mlir::ValueRange(); + } + mlir::ValueRange getCtrlQubitResults() { + return getOutCtrlQubits(); + } + }]; + + let extraClassDeclaration = extraBaseClassDeclaration; + +} + def MultiRZOp : Gate_Op<"multirz", [DifferentiableGate, AttrSizedOperandSegments, AttrSizedResultSegments ]> { let summary = "Apply an arbitrary multi Z rotation"; let description = [{ diff --git a/mlir/lib/Quantum/Transforms/ConversionPatterns.cpp b/mlir/lib/Quantum/Transforms/ConversionPatterns.cpp index 8240ab9d6b..cf2b509079 100644 --- a/mlir/lib/Quantum/Transforms/ConversionPatterns.cpp +++ b/mlir/lib/Quantum/Transforms/ConversionPatterns.cpp @@ -369,6 +369,35 @@ struct CustomOpPattern : public OpConversionPattern { } }; +struct GlobalPhaseOpPattern : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult matchAndRewrite(GlobalPhaseOp op, GlobalPhaseOpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override + { + Location loc = op.getLoc(); + MLIRContext *ctx = getContext(); + const TypeConverter *conv = getTypeConverter(); + auto modifiersPtr = getModifiersPtr(loc, rewriter, conv, op.getAdjointFlag(), + adaptor.getInCtrlQubits(), adaptor.getInCtrlValues()); + + std::string qirName = "__catalyst__qis__GlobalPhase"; + Type qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), {Float64Type::get(ctx), modifiersPtr.getType()}); + + LLVM::LLVMFuncOp fnDecl = ensureFunctionDeclaration(rewriter, op, qirName, qirSignature); + + SmallVector args; + args.insert(args.end(), adaptor.getParams()); + args.insert(args.end(), modifiersPtr); + + rewriter.create(loc, fnDecl, args); + rewriter.eraseOp(op); + + return success(); + } +}; + struct MultiRZOpPattern : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -866,6 +895,7 @@ void populateQIRConversionPatterns(TypeConverter &typeConverter, RewritePatternS patterns.add(typeConverter, patterns.getContext()); patterns.add(typeConverter, patterns.getContext()); patterns.add(typeConverter, patterns.getContext()); + patterns.add(typeConverter, patterns.getContext()); patterns.add(typeConverter, patterns.getContext()); patterns.add(typeConverter, patterns.getContext()); patterns.add(typeConverter, patterns.getContext()); diff --git a/mlir/test/Quantum/VerifierTest.mlir b/mlir/test/Quantum/VerifierTest.mlir index 8955b5caa0..46a1aa3364 100644 --- a/mlir/test/Quantum/VerifierTest.mlir +++ b/mlir/test/Quantum/VerifierTest.mlir @@ -67,15 +67,6 @@ func.func @custom(%f : f64, %q1 : !quantum.bit, %q2 : !quantum.bit) { // ----- -func.func @multirz1(%theta : f64) { - // expected-error@+1 {{must have at least 1 qubit}} - %err = quantum.multirz(%theta) : !quantum.bit - - return -} - -// ----- - func.func @multirz2(%q0 : !quantum.bit, %q1 : !quantum.bit, %theta : f64) { // expected-error@+1 {{number of qubits in input (2) and output (1) must be the same}} %err = quantum.multirz(%theta) %q0, %q1 : !quantum.bit @@ -94,15 +85,6 @@ func.func @multirz3(%q0 : !quantum.bit, %theta : f64) { // ----- -func.func @unitary1(%m : tensor<4x4xcomplex>) { - // expected-error@+1 {{must have at least 1 qubit}} - %err = quantum.unitary(%m: tensor<4x4xcomplex>) : !quantum.bit - - return -} - -// ----- - func.func @unitary2(%q0 : !quantum.bit, %q1 : !quantum.bit, %m : tensor<4x4xcomplex>) { // expected-error@+1 {{number of qubits in input (2) and output (1) must be the same}} %err = quantum.unitary(%m: tensor<4x4xcomplex>) %q0, %q1 : !quantum.bit diff --git a/runtime/Makefile b/runtime/Makefile index a5b9cb80d9..a221ad8e18 100644 --- a/runtime/Makefile +++ b/runtime/Makefile @@ -15,7 +15,7 @@ ENABLE_LIGHTNING_KOKKOS?=ON ENABLE_OPENQASM?=ON ENABLE_ASAN?=OFF # TODO: Update to v0.35.0 after the next lightning release. -LIGHTNING_GIT_TAG_VALUE?="86f22a768238c3f7c45a6f515b6cb61fc285a636" +LIGHTNING_GIT_TAG_VALUE?="03e465d96a665a2d1bcea2f1e6d79321e9d1c3fe" NPROC?=$(shell python3 -c "import os; print(os.cpu_count())") BUILD_TARGETS := rt_capi diff --git a/runtime/include/RuntimeCAPI.h b/runtime/include/RuntimeCAPI.h index d28fa99ed4..ee5fc47ab2 100644 --- a/runtime/include/RuntimeCAPI.h +++ b/runtime/include/RuntimeCAPI.h @@ -75,6 +75,7 @@ void __catalyst__qis__CRot(double, double, double, QUBIT *, QUBIT *, const Modif void __catalyst__qis__CSWAP(QUBIT *, QUBIT *, QUBIT *, const Modifiers *); void __catalyst__qis__Toffoli(QUBIT *, QUBIT *, QUBIT *, const Modifiers *); void __catalyst__qis__MultiRZ(double, const Modifiers *, int64_t, /*qubits*/...); +void __catalyst__qis__GlobalPhase(double, const Modifiers *); void __catalyst__qis__ISWAP(QUBIT *, QUBIT *, const Modifiers *); void __catalyst__qis__PSWAP(double, QUBIT *, QUBIT *, const Modifiers *); diff --git a/runtime/lib/capi/RuntimeCAPI.cpp b/runtime/lib/capi/RuntimeCAPI.cpp index 099986f1e6..3318613478 100644 --- a/runtime/lib/capi/RuntimeCAPI.cpp +++ b/runtime/lib/capi/RuntimeCAPI.cpp @@ -377,6 +377,12 @@ void __catalyst__qis__Gradient_params(MemRefT_int64_1d *params, int64_t numResul Catalyst::Runtime::getQuantumDevicePtr()->Gradient(mem_views, train_params); } +void __catalyst__qis__GlobalPhase(double phi, const Modifiers *modifiers) +{ + Catalyst::Runtime::getQuantumDevicePtr()->NamedOperation("GlobalPhase", {phi}, {}, + MODIFIERS_ARGS(modifiers)); +} + void __catalyst__qis__Identity(QUBIT *qubit, const Modifiers *modifiers) { Catalyst::Runtime::getQuantumDevicePtr()->NamedOperation( diff --git a/runtime/tests/Test_LightningCoreQIS.cpp b/runtime/tests/Test_LightningCoreQIS.cpp index 697b3e212b..bb22799e02 100644 --- a/runtime/tests/Test_LightningCoreQIS.cpp +++ b/runtime/tests/Test_LightningCoreQIS.cpp @@ -603,6 +603,31 @@ TEST_CASE("Test __catalyst__qis__ CRot, IsingXY and Toffoli", "[CoreQIS]") __catalyst__rt__finalize(); } +TEST_CASE("Test __catalyst__qis__ GlobalPhase", "[CoreQIS]") +{ + for (const auto &[rtd_lib, rtd_name, rtd_kwargs] : getDevices()) { + __catalyst__rt__initialize(); + __catalyst__rt__device_init((int8_t *)rtd_lib.c_str(), (int8_t *)rtd_name.c_str(), + (int8_t *)rtd_kwargs.c_str()); + + QirArray *qs = __catalyst__rt__qubit_allocate_array(1); + + __catalyst__qis__GlobalPhase(M_PI / 4, NO_MODIFIERS); + + MemRefT_CplxT_double_1d result = getState(2); + __catalyst__qis__State(&result, 0); + CplxT_double *state = result.data_allocated; + + CHECK((state[0].real == Approx(0.70710678).margin(1e-5) && + state[0].imag == Approx(-0.70710678).margin(1e-5))); + + freeState(result); + __catalyst__rt__qubit_release_array(qs); + __catalyst__rt__device_release(); + __catalyst__rt__finalize(); + } +} + TEST_CASE("Test __catalyst__qis__ Hadamard, PauliX, IsingYY, CRX, and Expval", "[CoreQIS]") { for (const auto &[rtd_lib, rtd_name, rtd_kwargs] : getDevices()) { diff --git a/setup.py b/setup.py index 866bd89cb1..3432a8491a 100644 --- a/setup.py +++ b/setup.py @@ -34,13 +34,25 @@ with open(".dep-versions") as f: lines = f.readlines() - jax_version = [line[4:].strip() for line in lines if "jax=" in line][0] - pl_str = "pennylane=" - pl_str_length = len(pl_str) - pl_version = [line[pl_str_length:].strip() for line in lines if pl_str in line][0] + jax_version = next(line[4:].strip() for line in lines if "jax=" in line) + pl_version = next((line[10:].strip() for line in lines if "pennylane=" in line), None) + lq_version = next((line[10:].strip() for line in lines if "lightning=" in line), None) + +pl_min_release = 0.35 +lq_min_release = pl_min_release + +if pl_version is not None: + pennylane_dep = f"pennylane @ git+https://github.com/pennylaneai/pennylane@{pl_version}" +else: + pennylane_dep = f"pennylane>={pl_min_release}" +if lq_version is not None: + lightning_dep = f"pennylane-lightning=={lq_version}" # use TestPyPI wheels to avoid rebuild +else: + lightning_dep = f"pennylane-lightning>={lq_min_release}" requirements = [ - f"pennylane @ git+https://github.com/pennylaneai/pennylane@{pl_version}", + pennylane_dep, + lightning_dep, f"jax=={jax_version}", f"jaxlib=={jax_version}", "tomlkit;python_version<'3.11'",