From ed2ec1233652e4f8a2e60dec63f3789d38587337 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Tue, 6 May 2025 16:41:36 -0400 Subject: [PATCH 1/4] initial commit, feature branch From 5c914980aefb2715e3813cb706867912624d102b Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Tue, 6 May 2025 16:57:16 -0400 Subject: [PATCH 2/4] removed legacy tests --- .../ops/op_math/test_controlled_ops.py | 1288 ----------------- .../ops/op_math/test_symbolic.py | 598 -------- .../ops/qubit/test_non_parametric_ops.py | 834 ----------- .../qubit/test_parametric_ops_multi_qubit.py | 551 ------- .../qubit/test_parametric_ops_single_qubit.py | 460 ------ .../ops/qubit/test_qchem_ops.py | 326 ----- .../resource_estimation/ops/test_identity.py | 191 --- .../templates/test_resource_basisrotation.py | 99 -- .../templates/test_resource_prepselprep.py | 190 --- .../templates/test_resource_qpe.py | 143 -- .../templates/test_resource_qubitization.py | 176 --- .../templates/test_resource_reflection.py | 106 -- .../templates/test_resource_select.py | 190 --- .../templates/test_resource_stateprep.py | 396 ----- .../templates/test_resource_trotter.py | 321 ---- .../templates/test_subroutines.py | 1041 ------------- .../test_resource_container.py | 392 ----- .../test_resource_operator.py | 132 -- .../test_resource_tracking.py | 357 ----- 19 files changed, 7791 deletions(-) delete mode 100644 pennylane/labs/tests/resource_estimation/ops/op_math/test_controlled_ops.py delete mode 100644 pennylane/labs/tests/resource_estimation/ops/op_math/test_symbolic.py delete mode 100644 pennylane/labs/tests/resource_estimation/ops/qubit/test_non_parametric_ops.py delete mode 100644 pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_multi_qubit.py delete mode 100644 pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_single_qubit.py delete mode 100644 pennylane/labs/tests/resource_estimation/ops/qubit/test_qchem_ops.py delete mode 100644 pennylane/labs/tests/resource_estimation/ops/test_identity.py delete mode 100644 pennylane/labs/tests/resource_estimation/templates/test_resource_basisrotation.py delete mode 100644 pennylane/labs/tests/resource_estimation/templates/test_resource_prepselprep.py delete mode 100644 pennylane/labs/tests/resource_estimation/templates/test_resource_qpe.py delete mode 100644 pennylane/labs/tests/resource_estimation/templates/test_resource_qubitization.py delete mode 100644 pennylane/labs/tests/resource_estimation/templates/test_resource_reflection.py delete mode 100644 pennylane/labs/tests/resource_estimation/templates/test_resource_select.py delete mode 100644 pennylane/labs/tests/resource_estimation/templates/test_resource_stateprep.py delete mode 100644 pennylane/labs/tests/resource_estimation/templates/test_resource_trotter.py delete mode 100644 pennylane/labs/tests/resource_estimation/templates/test_subroutines.py delete mode 100644 pennylane/labs/tests/resource_estimation/test_resource_container.py delete mode 100644 pennylane/labs/tests/resource_estimation/test_resource_operator.py delete mode 100644 pennylane/labs/tests/resource_estimation/test_resource_tracking.py diff --git a/pennylane/labs/tests/resource_estimation/ops/op_math/test_controlled_ops.py b/pennylane/labs/tests/resource_estimation/ops/op_math/test_controlled_ops.py deleted file mode 100644 index b41bacb935e..00000000000 --- a/pennylane/labs/tests/resource_estimation/ops/op_math/test_controlled_ops.py +++ /dev/null @@ -1,1288 +0,0 @@ -# Copyright 2025 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. -""" -Tests for controlled resource operators. -""" -import pytest - -import pennylane.labs.resource_estimation as re - -# pylint: disable=no-self-use, use-implicit-booleaness-not-comparison,too-many-arguments,too-many-positional-arguments - - -class TestResourceCH: - """Test the ResourceCH operation""" - - op = re.ResourceCH(wires=[0, 1]) - - def test_resources(self): - """Test that the resources method produces the expected resources.""" - - expected_resources = { - re.ResourceRY.resource_rep(): 2, - re.ResourceHadamard.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 1, - } - assert self.op.resources(**self.op.resource_params) == expected_resources - - def test_resource_rep(self): - """Test the resource_rep produces the correct compressed representation.""" - expected_rep = re.CompressedResourceOp(re.ResourceCH, {}) - assert self.op.resource_rep(**self.op.resource_params) == expected_rep - - def test_resource_params(self): - """Test that the resource_params are produced as expected.""" - expected_params = {} - assert self.op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - expected_res = {self.op.resource_rep(): 1} - op2 = re.ResourceAdjoint(self.op) - - assert self.op.adjoint_resource_decomp() == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceControlled.resource_rep(re.ResourceHadamard, {}, 2, 0, 0): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceControlled.resource_rep(re.ResourceHadamard, {}, 3, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceControlled.resource_rep(re.ResourceHadamard, {}, 4, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - self.op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - self.op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {op.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (5, {op.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op2 = re.ResourcePow(self.op, z) - - assert self.op.pow_resource_decomp(z) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceCY: - """Test the ResourceCY operation""" - - op = re.ResourceCY(wires=[0, 1]) - - def test_resources(self): - """Test that the resources method produces the expected resources.""" - - expected_resources = { - re.ResourceS.resource_rep(): 1, - re.ResourceCNOT.resource_rep(): 1, - re.ResourceAdjoint.resource_rep(re.ResourceS, {}): 1, - } - assert self.op.resources(**self.op.resource_params) == expected_resources - - def test_resource_rep(self): - """Test the resource_rep produces the correct compressed representation.""" - expected_rep = re.CompressedResourceOp(re.ResourceCY, {}) - assert self.op.resource_rep(**self.op.resource_params) == expected_rep - - def test_resource_params(self): - """Test that the resource_params are produced as expected.""" - expected_params = {} - assert self.op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - expected_res = {self.op.resource_rep(): 1} - op2 = re.ResourceAdjoint(self.op) - - assert self.op.adjoint_resource_decomp() == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceControlled.resource_rep(re.ResourceY, {}, 2, 0, 0): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceControlled.resource_rep(re.ResourceY, {}, 3, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceControlled.resource_rep(re.ResourceY, {}, 4, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - self.op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - self.op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {op.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (5, {op.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op2 = re.ResourcePow(self.op, z) - - assert self.op.pow_resource_decomp(z) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceCZ: - """Test the ResourceCZ operation""" - - op = re.ResourceCZ(wires=[0, 1]) - - def test_resources(self): - """Test that the resources method produces the expected resources.""" - - expected_resources = { - re.ResourceHadamard.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 1, - } - assert self.op.resources(**self.op.resource_params) == expected_resources - - def test_resource_rep(self): - """Test the resource_rep produces the correct compressed representation.""" - expected_rep = re.CompressedResourceOp(re.ResourceCZ, {}) - assert self.op.resource_rep() == expected_rep - - def test_resource_params(self): - """Test that the resource_params are produced as expected.""" - expected_params = {} - assert self.op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - expected_res = {self.op.resource_rep(): 1} - op2 = re.ResourceAdjoint(self.op) - - assert self.op.adjoint_resource_decomp() == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceCCZ.resource_rep(): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceControlled.resource_rep(re.ResourceZ, {}, 3, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceControlled.resource_rep(re.ResourceZ, {}, 4, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - self.op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - self.op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {op.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (5, {op.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op2 = re.ResourcePow(self.op, z) - - assert self.op.pow_resource_decomp(z) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceCSWAP: - """Test the ResourceCSWAP operation""" - - op = re.ResourceCSWAP(wires=[0, 1, 2]) - - def test_resources(self): - """Test that the resources method produces the expected resources.""" - expected_resources = { - re.ResourceToffoli.resource_rep(): 1, - re.ResourceCNOT.resource_rep(): 2, - } - assert self.op.resources(**self.op.resource_params) == expected_resources - - def test_resource_rep(self): - """Test the resource_rep produces the correct compressed representation.""" - expected_rep = re.CompressedResourceOp(re.ResourceCSWAP, {}) - assert self.op.resource_rep(**self.op.resource_params) == expected_rep - - def test_resource_params(self): - """Test that the resource_params are produced as expected.""" - expected_params = {} - assert self.op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - expected_res = {self.op.resource_rep(): 1} - op2 = re.ResourceAdjoint(self.op) - - assert self.op.adjoint_resource_decomp() == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceControlled.resource_rep(re.ResourceSWAP, {}, 2, 0, 0): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceControlled.resource_rep(re.ResourceSWAP, {}, 3, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceControlled.resource_rep(re.ResourceSWAP, {}, 4, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - self.op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - self.op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {op.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (5, {op.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op2 = re.ResourcePow(self.op, z) - - assert self.op.pow_resource_decomp(z) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceCCZ: - """Test the ResourceCZZ operation""" - - op = re.ResourceCCZ(wires=[0, 1, 2]) - - def test_resources(self): - """Test that the resources method produces the expected resources.""" - expected_resources = { - re.ResourceHadamard.resource_rep(): 2, - re.ResourceToffoli.resource_rep(): 1, - } - assert self.op.resources(**self.op.resource_params) == expected_resources - - def test_resource_rep(self): - """Test the resource_rep produces the correct compressed representation.""" - expected_rep = re.CompressedResourceOp(re.ResourceCCZ, {}) - assert self.op.resource_rep(**self.op.resource_params) == expected_rep - - def test_resource_params(self): - """Test that the resource_params are produced as expected.""" - expected_params = {} - assert self.op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - expected_res = {self.op.resource_rep(): 1} - op2 = re.ResourceAdjoint(self.op) - - assert self.op.adjoint_resource_decomp() == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceControlled.resource_rep(re.ResourceZ, {}, 3, 0, 0): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceControlled.resource_rep(re.ResourceZ, {}, 4, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceControlled.resource_rep(re.ResourceZ, {}, 5, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - self.op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - self.op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {op.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (5, {op.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op2 = re.ResourcePow(self.op, z) - - assert self.op.pow_resource_decomp(z) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceCNOT: - """Test ResourceCNOT operation""" - - op = re.ResourceCNOT([0, 1]) - - def test_resources(self): - """Test that the resources method is not implemented""" - with pytest.raises(re.ResourcesNotDefined): - self.op.resources() - - def test_resource_rep(self): - """Test the resource_rep produces the correct compressed representation.""" - expected = re.CompressedResourceOp(re.ResourceCNOT, {}) - assert self.op.resource_rep() == expected - - def test_resource_params(self): - """Test that the resource_params are produced as expected.""" - expected_params = {} - assert self.op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - expected_res = {self.op.resource_rep(): 1} - op2 = re.ResourceAdjoint(self.op) - - assert self.op.adjoint_resource_decomp() == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceToffoli.resource_rep(): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceMultiControlledX.resource_rep(3, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceMultiControlledX.resource_rep(4, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - self.op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - self.op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {op.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (5, {op.resource_rep(): 1}), - (8, {re.ResourceIdentity.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op2 = re.ResourcePow(self.op, z) - - assert self.op.pow_resource_decomp(z) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceToffoli: - """Test the ResourceToffoli operation""" - - op = re.ResourceToffoli(wires=[0, 1, 2]) - - def test_resources(self): - """Test that the resources method produces the expected resources.""" - - expected_resources = { - re.ResourceS.resource_rep(): 1, - re.ResourceT.resource_rep(): 2, - re.ResourceAdjoint.resource_rep(re.ResourceT, {}): 2, - re.ResourceCZ.resource_rep(): 1, - re.ResourceCNOT.resource_rep(): 9, - re.ResourceHadamard.resource_rep(): 3, - } - assert self.op.resources(**self.op.resource_params) == expected_resources - - def test_resource_rep(self): - """Test the resource_rep produces the correct compressed representation.""" - expected_rep = re.CompressedResourceOp(re.ResourceToffoli, {}) - assert self.op.resource_rep(**self.op.resource_params) == expected_rep - - def test_resource_params(self): - """Test that the resource_params are produced as expected.""" - expected_params = {} - assert self.op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - expected_res = {self.op.resource_rep(): 1} - op2 = re.ResourceAdjoint(self.op) - - assert self.op.adjoint_resource_decomp() == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceMultiControlledX.resource_rep(3, 0, 0): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceMultiControlledX.resource_rep(4, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceMultiControlledX.resource_rep(5, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - self.op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - self.op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {op.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (5, {op.resource_rep(): 1}), - (8, {re.ResourceIdentity.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op2 = re.ResourcePow(self.op, z) - - assert self.op.pow_resource_decomp(z) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceMultiControlledX: - """Test the ResourceMultiControlledX operation""" - - res_ops = ( - re.ResourceMultiControlledX(wires=[0, "t"], control_values=[1]), - re.ResourceMultiControlledX(wires=[0, 1, "t"], control_values=[1, 1]), - re.ResourceMultiControlledX(wires=[0, 1, 2, "t"], control_values=[1, 1, 1]), - re.ResourceMultiControlledX(wires=[0, 1, 2, 3, 4, "t"], control_values=[1, 1, 1, 1, 1]), - re.ResourceMultiControlledX(wires=[0, "t"], control_values=[0], work_wires=["w1"]), - re.ResourceMultiControlledX( - wires=[0, 1, "t"], control_values=[1, 0], work_wires=["w1", "w2"] - ), - re.ResourceMultiControlledX(wires=[0, 1, 2, "t"], control_values=[0, 0, 1]), - re.ResourceMultiControlledX( - wires=[0, 1, 2, 3, 4, "t"], - control_values=[1, 0, 0, 1, 0], - work_wires=["w1"], - ), - ) - - res_params = ( - (1, 0, 0), - (2, 0, 0), - (3, 0, 0), - (5, 0, 0), - (1, 1, 1), - (2, 1, 2), - (3, 2, 0), - (5, 3, 1), - ) - - expected_resources = ( - {re.ResourceCNOT.resource_rep(): 1}, - {re.ResourceToffoli.resource_rep(): 1}, - { - re.ResourceCNOT.resource_rep(): 2, - re.ResourceToffoli.resource_rep(): 1, - }, - {re.ResourceCNOT.resource_rep(): 69}, - { - re.ResourceX.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 1, - }, - { - re.ResourceX.resource_rep(): 2, - re.ResourceToffoli.resource_rep(): 1, - }, - { - re.ResourceX.resource_rep(): 4, - re.ResourceCNOT.resource_rep(): 2, - re.ResourceToffoli.resource_rep(): 1, - }, - { - re.ResourceX.resource_rep(): 6, - re.ResourceCNOT.resource_rep(): 69, - }, - ) - - @staticmethod - def _prep_params(num_control, num_control_values, num_work_wires): - return { - "num_ctrl_wires": num_control, - "num_ctrl_values": num_control_values, - "num_work_wires": num_work_wires, - } - - @pytest.mark.parametrize("params, expected_res", zip(res_params, expected_resources)) - def test_resources(self, params, expected_res): - """Test that the resources method produces the expected resources.""" - op_resource_params = self._prep_params(*params) - assert re.ResourceMultiControlledX.resources(**op_resource_params) == expected_res - - @pytest.mark.parametrize("op, params", zip(res_ops, res_params)) - def test_resource_rep(self, op, params): - """Test the resource_rep produces the correct compressed representation.""" - op_resource_params = self._prep_params(*params) - expected_rep = re.CompressedResourceOp(re.ResourceMultiControlledX, op_resource_params) - assert op.resource_rep(**op.resource_params) == expected_rep - - @pytest.mark.parametrize("op, params", zip(res_ops, res_params)) - def test_resource_params(self, op, params): - """Test that the resource_params are produced as expected.""" - expected_params = self._prep_params(*params) - assert op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - op = re.ResourceMultiControlledX( - wires=[0, 1, 2, 3, 4, "t"], - control_values=[1, 0, 0, 1, 0], - work_wires=["w1"], - ) - - expected_res = {op.resource_rep(**op.resource_params): 1} - op2 = re.ResourceAdjoint(op) - - assert op.adjoint_resource_decomp(**op.resource_params) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceToffoli.resource_rep(): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["work1"], - { - re.ResourceCNOT.resource_rep(): 2, - re.ResourceToffoli.resource_rep(): 1, - }, - ), - ( - ["c1", "c2", "c3", "c4"], - [1, 0, 0, 1], - ["work1", "work2"], - { - re.ResourceX.resource_rep(): 4, - re.ResourceCNOT.resource_rep(): 69, - }, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - op = re.ResourceMultiControlledX(wires=[0, "t"], control_values=[1]) - - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires, **op.resource_params - ) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {re.ResourceMultiControlledX.resource_rep(5, 3, 1): 1}), - (2, {}), - (5, {re.ResourceMultiControlledX.resource_rep(5, 3, 1): 1}), - (6, {}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op = re.ResourceMultiControlledX( - wires=[0, 1, 2, 3, 4, "t"], - control_values=[1, 0, 0, 1, 0], - work_wires=["w1"], - ) - - op2 = re.ResourcePow(op, z) - - assert op.pow_resource_decomp(z, **op.resource_params) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceCRX: - """Test the ResourceCRX operation""" - - op = re.ResourceCRX(phi=1.23, wires=[0, 1]) - - def test_resources(self): - """Test that the resources method produces the expected resources.""" - - expected_resources = { - re.ResourceRZ.resource_rep(): 2, - re.ResourceHadamard.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 2, - } - assert self.op.resources(**self.op.resource_params) == expected_resources - - def test_resource_rep(self): - """Test the resource_rep produces the correct compressed representation.""" - expected_rep = re.CompressedResourceOp(re.ResourceCRX, {}) - assert self.op.resource_rep(**self.op.resource_params) == expected_rep - - def test_resource_params(self): - """Test that the resource_params are produced as expected.""" - expected_params = {} - assert self.op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - expected_res = {self.op.resource_rep(): 1} - op2 = re.ResourceAdjoint(self.op) - - assert self.op.adjoint_resource_decomp() == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceControlled.resource_rep(re.ResourceRX, {}, 2, 0, 0): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceControlled.resource_rep(re.ResourceRX, {}, 3, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceControlled.resource_rep(re.ResourceRX, {}, 4, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - self.op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - self.op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {op.resource_rep(): 1}), - (2, {op.resource_rep(): 1}), - (5, {op.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op2 = re.ResourcePow(self.op, z) - - assert self.op.pow_resource_decomp(z) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceCRY: - """Test the ResourceCRY operation""" - - op = re.ResourceCRY(phi=1.23, wires=[0, 1]) - - def test_resources(self): - """Test that the resources method produces the expected resources.""" - - expected_resources = { - re.ResourceRY.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 2, - } - assert self.op.resources(**self.op.resource_params) == expected_resources - - def test_resource_rep(self): - """Test the resource_rep produces the correct compressed representation.""" - expected_rep = re.CompressedResourceOp(re.ResourceCRY, {}) - assert self.op.resource_rep(**self.op.resource_params) == expected_rep - - def test_resource_params(self): - """Test that the resource_params are produced as expected.""" - expected_params = {} - assert self.op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - expected_res = {self.op.resource_rep(): 1} - op2 = re.ResourceAdjoint(self.op) - - assert self.op.adjoint_resource_decomp() == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceControlled.resource_rep(re.ResourceRY, {}, 2, 0, 0): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceControlled.resource_rep(re.ResourceRY, {}, 3, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceControlled.resource_rep(re.ResourceRY, {}, 4, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - self.op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - self.op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {op.resource_rep(): 1}), - (2, {op.resource_rep(): 1}), - (5, {op.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op2 = re.ResourcePow(self.op, z) - - assert self.op.pow_resource_decomp(z) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceCRZ: - """Test the ResourceCRZ operation""" - - op = re.ResourceCRZ(phi=1.23, wires=[0, 1]) - - def test_resources(self): - """Test that the resources method produces the expected resources.""" - - expected_resources = { - re.ResourceRZ.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 2, - } - assert self.op.resources(**self.op.resource_params) == expected_resources - - def test_resource_rep(self): - """Test the resource_rep produces the correct compressed representation.""" - expected_rep = re.CompressedResourceOp(re.ResourceCRZ, {}) - assert self.op.resource_rep(**self.op.resource_params) == expected_rep - - def test_resource_params(self): - """Test that the resource_params are produced as expected.""" - expected_params = {} - assert self.op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - expected_res = {self.op.resource_rep(): 1} - op2 = re.ResourceAdjoint(self.op) - - assert self.op.adjoint_resource_decomp() == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceControlled.resource_rep(re.ResourceRZ, {}, 2, 0, 0): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceControlled.resource_rep(re.ResourceRZ, {}, 3, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceControlled.resource_rep(re.ResourceRZ, {}, 4, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - self.op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - self.op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {op.resource_rep(): 1}), - (2, {op.resource_rep(): 1}), - (5, {op.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op2 = re.ResourcePow(self.op, z) - - assert self.op.pow_resource_decomp(z) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceCRot: - """Test the ResourceCRot operation""" - - op = re.ResourceCRot(0.1, 0.2, 0.3, wires=[0, 1]) - - def test_resources(self): - """Test that the resources method produces the expected resources.""" - expected_resources = { - re.ResourceRY.resource_rep(): 2, - re.ResourceRZ.resource_rep(): 3, - re.ResourceCNOT.resource_rep(): 2, - } - assert self.op.resources(**self.op.resource_params) == expected_resources - - def test_resource_rep(self): - """Test the resource_rep produces the correct compressed representation.""" - expected_rep = re.CompressedResourceOp(re.ResourceCRot, {}) - assert self.op.resource_rep(**self.op.resource_params) == expected_rep - - def test_resource_params(self): - """Test that the resource_params are produced as expected.""" - expected_params = {} - assert self.op.resource_params == expected_params - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - expected_res = {self.op.resource_rep(): 1} - op2 = re.ResourceAdjoint(self.op) - - assert self.op.adjoint_resource_decomp() == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceControlled.resource_rep(re.ResourceRot, {}, 2, 0, 0): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceControlled.resource_rep(re.ResourceRot, {}, 3, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceControlled.resource_rep(re.ResourceRot, {}, 4, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op2 = re.ResourceControlled( - self.op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - self.op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {op.resource_rep(): 1}), - (2, {op.resource_rep(): 1}), - (5, {op.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op2 = re.ResourcePow(self.op, z) - - assert self.op.pow_resource_decomp(z) == expected_res - assert op2.resources(**op2.resource_params) == expected_res - - -class TestResourceControlledPhaseShift: - """Test ResourceControlledPhaseShift""" - - params = [(1.2, [0, 1]), (2.4, [2, 3])] - - @pytest.mark.parametrize("phi, wires", params) - def test_resources(self, phi, wires): - """Test the resources method""" - - op = re.ResourceControlledPhaseShift(phi, wires) - - expected = { - re.CompressedResourceOp(re.ResourceCNOT, {}): 2, - re.CompressedResourceOp(re.ResourceRZ, {}): 3, - } - - assert op.resources(**op.resource_params) == expected - - @pytest.mark.parametrize("phi, wires", params) - def test_resource_params(self, phi, wires): - """Test the resource parameters""" - - op = re.ResourceControlledPhaseShift(phi, wires) - assert op.resource_params == {} # pylint: disable=use-implicit-booleaness-not-comparison - - @pytest.mark.parametrize("phi, wires", params) - def test_resource_rep(self, phi, wires): - """Test the compressed representation""" - - op = re.ResourceControlledPhaseShift(phi, wires) - expected = re.CompressedResourceOp(re.ResourceControlledPhaseShift, {}) - - assert op.resource_rep() == expected - - @pytest.mark.parametrize("phi, wires", params) - def test_resource_rep_from_op(self, phi, wires): - """Test resource_rep_from_op method""" - - op = re.ResourceControlledPhaseShift(phi, wires) - assert op.resource_rep_from_op() == re.ResourceControlledPhaseShift.resource_rep( - **op.resource_params - ) - - @pytest.mark.parametrize("phi, wires", params) - def test_resources_from_rep(self, phi, wires): - """Compute the resources from the compressed representation""" - - op = re.ResourceControlledPhaseShift(phi, wires) - - expected = { - re.CompressedResourceOp(re.ResourceCNOT, {}): 2, - re.CompressedResourceOp(re.ResourceRZ, {}): 3, - } - - op_compressed_rep = op.resource_rep_from_op() - op_resource_params = op_compressed_rep.params - op_compressed_rep_type = op_compressed_rep.op_type - - assert op_compressed_rep_type.resources(**op_resource_params) == expected - - @pytest.mark.parametrize("phi, wires", params) - def test_adjoint_decomp(self, phi, wires): - """Test that the adjoint resources are correct.""" - - op = re.ResourceControlledPhaseShift(phi, wires) - adjoint = re.ResourceAdjoint(op) - - assert re.get_resources(op) == re.get_resources(adjoint) - - @pytest.mark.parametrize("phi, wires", params) - def test_pow_decomp(self, phi, wires): - """Test that the adjoint resources are correct.""" - - op = re.ResourceControlledPhaseShift(phi, wires) - pow = re.ResourcePow(op, 2) - - assert re.get_resources(op) == re.get_resources(pow) - - ctrl_data = ( - ( - ["c1"], - [1], - [], - {re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 2, 0, 0): 1}, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - {re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 3, 0, 1): 1}, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 4, 2, 2): 1}, - ), - ) - - @pytest.mark.parametrize("phi, wires", params) - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled( - self, phi, wires, ctrl_wires, ctrl_values, work_wires, expected_res - ): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourceControlledPhaseShift(phi, wires) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res diff --git a/pennylane/labs/tests/resource_estimation/ops/op_math/test_symbolic.py b/pennylane/labs/tests/resource_estimation/ops/op_math/test_symbolic.py deleted file mode 100644 index 8877d5bda57..00000000000 --- a/pennylane/labs/tests/resource_estimation/ops/op_math/test_symbolic.py +++ /dev/null @@ -1,598 +0,0 @@ -# Copyright 2025 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. -""" -Tests for symbolic resource operators. -""" - -import pytest - -import pennylane as qml -import pennylane.labs.resource_estimation as re -from pennylane.labs.resource_estimation.ops.op_math.symbolic import ( - _extract_exp_params, - _resources_from_pauli_sentence, -) -from pennylane.operation import Operation -from pennylane.pauli import PauliSentence, PauliWord - -# pylint: disable=protected-access,no-self-use,arguments-differ - - -class DummyOp(re.ResourceOperator, Operation): - """Dummy ResourceOperator child class which implements the - :code:`exp_resource_decomp` method.""" - - def __init__(self, a, b, wires=(0,)): - self.a = a - self.b = b - super().__init__(wires=wires) - - @staticmethod - def _resource_decomp(a, b, **kwargs): - h = re.ResourceHadamard.resource_rep() - cnot = re.ResourceCNOT.resource_rep() - - return {h: a, cnot: b} - - @classmethod - def exp_resource_decomp(cls, coeff, num_steps, a, b): # pylint: disable=unused-argument - return cls.resources(a + 1, b + 1) - - @property - def resource_params(self) -> dict: - return {"a": self.a, "b": self.b} - - @classmethod - def resource_rep(cls, a, b): - return re.CompressedResourceOp(cls, {"a": a, "b": b}) - - -z_pauli_rep = qml.Z(0).pauli_rep -lc_op = qml.ops.LinearCombination( - [1.11, 0.12, -3.4, 5], [qml.X(0) @ qml.X(1), qml.Z(2), qml.Y(0) @ qml.Y(1), qml.I((0, 1, 2))] -) -exp_params_data = ( - ( - lc_op, - { - "base_class": qml.ops.LinearCombination, - "base_params": {}, - "base_pauli_rep": lc_op.pauli_rep, - "coeff": 1.2j, - "num_steps": 3, - }, - ), - ( - re.ResourceQFT(range(10)), - { - "base_class": re.ResourceQFT, - "base_params": {"num_wires": 10}, - "base_pauli_rep": None, - "coeff": 1.2j, - "num_steps": 3, - }, - ), -) - - -class TestResourceAdjoint: - """Tests for ResourceAdjoint""" - - adjoint_ops = [ - re.ResourceAdjoint(re.ResourceQFT([0, 1])), - re.ResourceAdjoint(re.ResourceAdjoint(re.ResourceQFT([0, 1]))), - re.ResourceAdjoint(re.ResourcePow(re.ResourceX(0), 5)), - ] - - expected_params = [ - {"base_class": re.ResourceQFT, "base_params": {"num_wires": 2}}, - { - "base_class": re.ResourceAdjoint, - "base_params": {"base_class": re.ResourceQFT, "base_params": {"num_wires": 2}}, - }, - { - "base_class": re.ResourcePow, - "base_params": {"base_class": re.ResourceX, "base_params": {}, "z": 5}, - }, - ] - - @pytest.mark.parametrize("op, expected", zip(adjoint_ops, expected_params)) - def test_resource_params(self, op, expected): - """Test that the resources are correct""" - assert op.resource_params == expected - - expected_names = [ - "Adjoint(QFT(2))", - "Adjoint(Adjoint(QFT(2)))", - "Adjoint(Pow(X, 5))", - ] - - @pytest.mark.parametrize("op, expected", zip(adjoint_ops, expected_names)) - def test_tracking_name(self, op, expected): - """Test that the tracking name is correct""" - name = op.tracking_name_from_op() - assert name == expected - - @pytest.mark.parametrize( - "nested_op, expected_op", - [ - ( - re.ResourceAdjoint(re.ResourceAdjoint(re.ResourceQFT([0, 1, 2]))), - re.ResourceQFT([0, 1, 2]), - ), - ( - re.ResourceAdjoint( - re.ResourceAdjoint(re.ResourceAdjoint(re.ResourceQFT([0, 1, 2]))) - ), - re.ResourceAdjoint(re.ResourceQFT([0, 1, 2])), - ), - ( - re.ResourceAdjoint( - re.ResourceAdjoint( - re.ResourceAdjoint(re.ResourceAdjoint(re.ResourceQFT([0, 1, 2]))) - ) - ), - re.ResourceQFT([0, 1, 2]), - ), - ( - re.ResourceAdjoint( - re.ResourceAdjoint( - re.ResourceAdjoint( - re.ResourceAdjoint(re.ResourceAdjoint(re.ResourceQFT([0, 1, 2]))) - ) - ) - ), - re.ResourceAdjoint(re.ResourceQFT([0, 1, 2])), - ), - ], - ) - def test_nested_adjoints(self, nested_op, expected_op): - """Test the resources of nested Adjoints.""" - assert re.get_resources(nested_op) == re.get_resources(expected_op) - - expected_resources = [ - re.Resources(gate_types={"Adjoint(QFT(2))": 1}, num_gates=1, num_wires=2), - re.Resources(gate_types={"Adjoint(Adjoint(QFT(2)))": 1}, num_gates=1, num_wires=2), - re.Resources(gate_types={"Adjoint(Pow(X, 5))": 1}, num_gates=1, num_wires=1), - ] - - @pytest.mark.parametrize("op, expected", zip(adjoint_ops, expected_resources)) - def test_tracking(self, op, expected): - """Test that adjoints can be tracked.""" - tracking_name = op.tracking_name_from_op() - gate_set = {tracking_name} - - assert re.get_resources(op, gate_set=gate_set) == expected - - -class TestResourceControlled: - """Tests for ResourceControlled""" - - controlled_ops = [ - re.ResourceControlled(re.ResourceQFT([0, 1]), control_wires=[2]), - re.ResourceControlled( - re.ResourceControlled(re.ResourceQFT([0, 1]), control_wires=[2]), control_wires=[3] - ), - re.ResourceControlled(re.ResourceQFT([0, 1]), control_wires=[2, 3], control_values=[0, 1]), - re.ResourceControlled( - re.ResourceAdjoint(re.ResourceQFT([0, 1])), - control_wires=[2, 3], - control_values=[0, 1], - work_wires=[4], - ), - ] - - expected_params = [ - { - "base_class": re.ResourceQFT, - "base_params": {"num_wires": 2}, - "num_ctrl_wires": 1, - "num_ctrl_values": 0, - "num_work_wires": 0, - }, - { - "base_class": re.ResourceControlled, - "base_params": { - "base_class": re.ResourceQFT, - "base_params": {"num_wires": 2}, - "num_ctrl_wires": 1, - "num_ctrl_values": 0, - "num_work_wires": 0, - }, - "num_ctrl_wires": 1, - "num_ctrl_values": 0, - "num_work_wires": 0, - }, - { - "base_class": re.ResourceQFT, - "base_params": {"num_wires": 2}, - "num_ctrl_wires": 2, - "num_ctrl_values": 1, - "num_work_wires": 0, - }, - { - "base_class": re.ResourceAdjoint, - "base_params": {"base_class": re.ResourceQFT, "base_params": {"num_wires": 2}}, - "num_ctrl_wires": 2, - "num_ctrl_values": 1, - "num_work_wires": 1, - }, - ] - - @pytest.mark.parametrize("op, expected", zip(controlled_ops, expected_params)) - def test_resource_params(self, op, expected): - """Test that the resources are correct""" - assert op.resource_params == expected - - expected_names = [ - "C(QFT(2),1,0,0)", - "C(C(QFT(2),1,0,0),1,0,0)", - "C(QFT(2),2,1,0)", - "C(Adjoint(QFT(2)),2,1,1)", - ] - - @pytest.mark.parametrize("op, expected", zip(controlled_ops, expected_names)) - def test_tracking_name(self, op, expected): - """Test that the tracking name is correct""" - name = op.tracking_name_from_op() - assert name == expected - - @pytest.mark.parametrize( - "nested_op, expected_op", - [ - ( - re.ResourceControlled( - re.ResourceControlled(re.ResourceX(0), control_wires=[1]), control_wires=[2] - ), - re.ResourceToffoli([0, 1, 2]), - ), - ( - re.ResourceControlled( - re.ResourceControlled(re.ResourceX(0), control_wires=[1]), control_wires=[2] - ), - re.ResourceControlled(re.ResourceX(0), control_wires=[1, 2]), - ), - ], - ) - def test_nested_controls(self, nested_op, expected_op): - """Test the resources for nested Controlled operators.""" - assert re.get_resources(nested_op) == re.get_resources(expected_op) - - expected_resources = [ - re.Resources(gate_types={"C(QFT(2),1,0,0)": 1}, num_gates=1, num_wires=3), - re.Resources(gate_types={"C(C(QFT(2),1,0,0),1,0,0)": 1}, num_gates=1, num_wires=4), - re.Resources(gate_types={"C(QFT(2),2,1,0)": 1}, num_gates=1, num_wires=4), - re.Resources( - gate_types={"C(Adjoint(QFT(2)),2,1,1)": 1}, num_gates=1, num_wires=4 - ), # PL does not count work wires for controlled operators - ] - - @pytest.mark.parametrize("op, expected", zip(controlled_ops, expected_resources)) - def test_tracking(self, op, expected): - """Test that adjoints can be tracked.""" - tracking_name = op.tracking_name_from_op() - gate_set = {tracking_name} - - assert re.get_resources(op, gate_set=gate_set) == expected - - -class TestResourcePow: - """Tests for ResourcePow""" - - pow_ops = [ - re.ResourcePow(re.ResourceQFT([0, 1]), 2), - re.ResourcePow(re.ResourceAdjoint(re.ResourceQFT([0, 1])), 2), - re.ResourcePow(re.ResourcePow(re.ResourceQFT([0, 1]), 2), 3), - ] - - expected_params = [ - {"base_class": re.ResourceQFT, "base_params": {"num_wires": 2}, "z": 2}, - { - "base_class": re.ResourceAdjoint, - "base_params": {"base_class": re.ResourceQFT, "base_params": {"num_wires": 2}}, - "z": 2, - }, - { - "base_class": re.ResourcePow, - "base_params": {"base_class": re.ResourceQFT, "base_params": {"num_wires": 2}, "z": 2}, - "z": 3, - }, - ] - - @pytest.mark.parametrize("op, expected", zip(pow_ops, expected_params)) - def test_resource_params(self, op, expected): - """Test that the resources are correct""" - assert op.resource_params == expected - - expected_names = [ - "Pow(QFT(2), 2)", - "Pow(Adjoint(QFT(2)), 2)", - "Pow(Pow(QFT(2), 2), 3)", - ] - - @pytest.mark.parametrize("op, expected", zip(pow_ops, expected_names)) - def test_tracking_name(self, op, expected): - """Test that the tracking name is correct""" - rep = op.resource_rep_from_op() - name = rep.op_type.tracking_name(**rep.params) - assert name == expected - - expected_resources = [ - re.Resources(gate_types={"Pow(QFT(2), 2)": 1}, num_gates=1, num_wires=2), - re.Resources(gate_types={"Pow(Adjoint(QFT(2)), 2)": 1}, num_gates=1, num_wires=2), - re.Resources(gate_types={"Pow(Pow(QFT(2), 2), 3)": 1}, num_gates=1, num_wires=2), - ] - - @pytest.mark.parametrize("op, expected", zip(pow_ops, expected_resources)) - def test_tracking(self, op, expected): - """Test that adjoints can be tracked.""" - tracking_name = op.tracking_name_from_op() - gate_set = {tracking_name} - - assert re.get_resources(op, gate_set=gate_set) == expected - - @pytest.mark.parametrize( - "nested_op, expected_op", - [ - ( - re.ResourcePow(re.ResourcePow(re.ResourceQFT([0, 1]), 2), 2), - re.ResourcePow(re.ResourceQFT([0, 1]), 4), - ), - ( - re.ResourcePow(re.ResourcePow(re.ResourcePow(re.ResourceQFT([0, 1]), 2), 2), 2), - re.ResourcePow(re.ResourceQFT([0, 1]), 8), - ), - ( - re.ResourcePow( - re.ResourcePow(re.ResourcePow(re.ResourcePow(re.ResourceQFT([0, 1]), 2), 2), 2), - 2, - ), - re.ResourcePow(re.ResourceQFT([0, 1]), 16), - ), - ], - ) - def test_nested_pow(self, nested_op, expected_op): - """Test the resources for nested Pow operators.""" - assert re.get_resources(nested_op) == re.get_resources(expected_op) - - -class TestResourceProd: - """Test ResourceProd""" - - op_data = ( - re.ResourceProd(re.ResourceX(0), re.ResourceHadamard(1)), - re.ResourceProd( - re.ResourceQFT(wires=[0, 1, 2]), - re.ResourceAdjoint(re.ResourceZ(0)), - re.ResourceControlled(re.ResourcePhaseShift(1.23, 0), control_wires=["c1", "c2"]), - ), - re.ResourceProd( - re.ResourceCNOT([0, 1]), - re.ResourceExp(re.ResourceZ(0), 0.1j), - re.ResourceProd(re.ResourceX(1), re.ResourceY(2)), - re.ResourceZ(3), - ), - ) - - resource_data = ( - { - re.ResourceX.resource_rep(): 1, - re.ResourceHadamard.resource_rep(): 1, - }, - { - re.ResourceQFT.resource_rep(3): 1, - re.ResourceAdjoint.resource_rep(re.ResourceZ, {}): 1, - re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 2, 0, 0): 1, - }, - { - re.ResourceCNOT.resource_rep(): 1, - re.ResourceExp.resource_rep(re.ResourceZ, {}, z_pauli_rep, 0.1j, None): 1, - re.ResourceProd.resource_rep( - cmpr_factors=(re.ResourceX.resource_rep(), re.ResourceY.resource_rep()) - ): 1, - re.ResourceZ.resource_rep(): 1, - }, - ) - - resource_params_data = ( - { - "cmpr_factors": ( - re.ResourceX.resource_rep(), - re.ResourceHadamard.resource_rep(), - ), - }, - { - "cmpr_factors": ( - re.ResourceQFT.resource_rep(3), - re.ResourceAdjoint.resource_rep(re.ResourceZ, {}), - re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 2, 0, 0), - ), - }, - { - "cmpr_factors": ( - re.ResourceCNOT.resource_rep(), - re.ResourceExp.resource_rep(re.ResourceZ, {}, z_pauli_rep, 0.1j, None), - re.ResourceProd.resource_rep( - cmpr_factors=(re.ResourceX.resource_rep(), re.ResourceY.resource_rep()) - ), - re.ResourceZ.resource_rep(), - ), - }, - ) - - @pytest.mark.parametrize( - "op, params, expected_res", zip(op_data, resource_params_data, resource_data) - ) - def test_resources(self, op, params, expected_res): - """Test the resources method returns the correct dictionary""" - res_from_op = op.resources(**op.resource_params) - res_from_func = re.ResourceProd.resources(**params) - - assert res_from_op == expected_res - assert res_from_func == expected_res - - @pytest.mark.parametrize("op, expected_params", zip(op_data, resource_params_data)) - def test_resource_params(self, op, expected_params): - """Test that the resource params are correct""" - assert op.resource_params == expected_params - - @pytest.mark.parametrize("expected_params", resource_params_data) - def test_resource_rep(self, expected_params): - """Test the resource_rep returns the correct CompressedResourceOp""" - expected = re.CompressedResourceOp(re.ResourceProd, expected_params) - assert re.ResourceProd.resource_rep(**expected_params) == expected - - -class TestResourceExp: - """Test for ResourceExp""" - - @pytest.mark.parametrize("op, expected", exp_params_data) - def test_resource_params(self, op, expected): - """Test that the resource_params method produces the expected parameters.""" - exp_op = re.ResourceExp(op, 1.2j, num_steps=3) - extracted_params = exp_op.resource_params - assert extracted_params == expected - - @pytest.mark.parametrize("op, expected_params", exp_params_data) - def test_resource_rep(self, op, expected_params): - """Test that the resource_rep method produces the correct compressed representation.""" - exp_op = re.ResourceExp(op, 1.2j, num_steps=3) - computed_rep = exp_op.resource_rep(**expected_params) - expected_rep = re.CompressedResourceOp( - re.ResourceExp, expected_params, name=exp_op.tracking_name_from_op() - ) - - assert expected_rep == computed_rep - - exp_res_data = ( - ( - re.ResourceExp(lc_op, 1.5j), - { - re.ResourcePauliRot.resource_rep("XX"): 1, - re.ResourcePauliRot.resource_rep("YY"): 1, - re.ResourcePauliRot.resource_rep("Z"): 1, - re.ResourcePauliRot.resource_rep(""): 1, - }, - ), - ( - re.ResourceExp(DummyOp(2, 3, wires=[1, 2, 3]), 0.1j, num_steps=5), - { - re.ResourceHadamard.resource_rep(): 3, - re.ResourceCNOT.resource_rep(): 4, - }, - ), - ) - - @pytest.mark.parametrize("op, expected_resources", exp_res_data) - def test_resources_decomp(self, op, expected_resources): - """Test that the _resources_decomp method works as expected.""" - computed_resources = op._resource_decomp(**op.resource_params) - assert computed_resources == expected_resources - - @pytest.mark.parametrize( - "op, z, expected_resources", - ( - ( - re.ResourceExp(lc_op, 1.5j), - 1, - {re.ResourceExp.resource_rep(type(lc_op), {}, lc_op.pauli_rep, 1.5j, None): 1}, - ), - ( - re.ResourceExp(lc_op, 1.5j), - 2, - {re.ResourceExp.resource_rep(type(lc_op), {}, lc_op.pauli_rep, 3j, None): 1}, - ), - ( - re.ResourceExp(lc_op, 1.5j), - 7, - {re.ResourceExp.resource_rep(type(lc_op), {}, lc_op.pauli_rep, 10.5j, None): 1}, - ), - ( - re.ResourceExp(DummyOp(2, 3, wires=[1, 2, 3]), 0.1j, num_steps=5), - 1, - {re.ResourceExp.resource_rep(DummyOp, {"a": 2, "b": 3}, None, 0.1j, 5): 1}, - ), - ( - re.ResourceExp(DummyOp(2, 3, wires=[1, 2, 3]), 0.1j, num_steps=5), - 4, - {re.ResourceExp.resource_rep(DummyOp, {"a": 2, "b": 3}, None, 0.4j, 5): 1}, - ), - ( - re.ResourceExp(DummyOp(2, 3, wires=[1, 2, 3]), 0.1j, num_steps=5), - 8, - {re.ResourceExp.resource_rep(DummyOp, {"a": 2, "b": 3}, None, 0.8j, 5): 1}, - ), - ), - ) - def test_pow_resources(self, op, z, expected_resources): - """Test that the pow resource decomp method works as expected.""" - params = op.resource_params - computed_resources = op.pow_resource_decomp(z, **params) - assert computed_resources == expected_resources - - -@pytest.mark.parametrize("base_op, expected_params", exp_params_data) -def test_extract_exp_params(base_op, expected_params): - """Test the private _extract_exp_params method behaves as expected""" - extracted_params = _extract_exp_params(base_op, scalar=1.2j, num_steps=3) - assert extracted_params == expected_params - - -def test_extract_exp_params_raises_error(): - """Test that the private _extract_exp_params method raises an error if the base operator - isnt compatible with ResourceExp.""" - with pytest.raises(ValueError, match="Cannot obtain resources for the exponential of"): - _ = _extract_exp_params(qml.QFT(range(10)), 1j, 5) - - -@pytest.mark.parametrize( - "ps, expected_res", - ( - (PauliSentence({}), {}), - ( - PauliSentence( - { - PauliWord({0: "I", 2: "I", 3: "I"}): 0.12, - PauliWord({0: "X", 2: "I", 3: "I"}): -3.4, - PauliWord({0: "I", 2: "Y", 3: "I"}): 56, - PauliWord({0: "I", 2: "I", 3: "Z"}): 0.78, - } - ), - { - re.ResourcePauliRot.resource_rep(""): 1, - re.ResourcePauliRot.resource_rep("X"): 1, - re.ResourcePauliRot.resource_rep("Y"): 1, - re.ResourcePauliRot.resource_rep("Z"): 1, - }, - ), - ( - PauliSentence( - { - PauliWord({0: "X", 2: "X", 3: "X"}): 0.12, - PauliWord({0: "Y", 2: "Y", 3: "Y"}): -3.4, - PauliWord({0: "X", 2: "Y", 3: "Z"}): 56, - } - ), - { - re.ResourcePauliRot.resource_rep("XXX"): 1, - re.ResourcePauliRot.resource_rep("YYY"): 1, - re.ResourcePauliRot.resource_rep("XYZ"): 1, - }, - ), - ), -) -def test_resources_from_pauli_sentence(ps, expected_res): - """Test that the private function resources_from_pauli_sentence works correcty""" - extracted_res = _resources_from_pauli_sentence(ps) - assert extracted_res == expected_res diff --git a/pennylane/labs/tests/resource_estimation/ops/qubit/test_non_parametric_ops.py b/pennylane/labs/tests/resource_estimation/ops/qubit/test_non_parametric_ops.py deleted file mode 100644 index e23c3ecac9f..00000000000 --- a/pennylane/labs/tests/resource_estimation/ops/qubit/test_non_parametric_ops.py +++ /dev/null @@ -1,834 +0,0 @@ -# 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. -""" -Tests for non parametric resource operators. -""" -import pytest - -import pennylane.labs.resource_estimation as re - -# pylint: disable=no-self-use,use-implicit-booleaness-not-comparison - - -class TestHadamard: - """Tests for ResourceHadamard""" - - def test_resources(self): - """Test that ResourceHadamard does not implement a decomposition""" - op = re.ResourceHadamard(0) - with pytest.raises(re.ResourcesNotDefined): - op.resources() - - def test_resource_params(self): - """Test that the resource params are correct""" - op = re.ResourceHadamard(0) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compact representation is correct""" - expected = re.CompressedResourceOp(re.ResourceHadamard, {}) - assert re.ResourceHadamard.resource_rep() == expected - - def test_adjoint_decomp(self): - """Test that the adjoint decomposition is correct.""" - h = re.ResourceHadamard(0) - h_dag = re.ResourceAdjoint(re.ResourceHadamard(0)) - - assert re.get_resources(h) == re.get_resources(h_dag) - - ctrl_data = ( - ( - ["c1"], - [1], - [], - { - re.ResourceCH.resource_rep(): 1, - }, - ), - ( - ["c1"], - [0], - [], - { - re.ResourceCH.resource_rep(): 1, - re.ResourceX.resource_rep(): 2, - }, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - { - re.ResourceRY.resource_rep(): 2, - re.ResourceHadamard.resource_rep(): 2, - re.ResourceMultiControlledX.resource_rep(2, 0, 1): 1, - }, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - { - re.ResourceRY.resource_rep(): 2, - re.ResourceHadamard.resource_rep(): 2, - re.ResourceMultiControlledX.resource_rep(3, 2, 2): 1, - }, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourceHadamard(0) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {re.ResourceHadamard.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (3, {re.ResourceHadamard.resource_rep(): 1}), - (4, {re.ResourceIdentity.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_pow_decomp(self, z, expected_res): - """Test that the pow decomposition is correct.""" - op = re.ResourceHadamard(0) - assert op.pow_resource_decomp(z) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res - - -class TestSWAP: - """Tests for ResourceSWAP""" - - def test_resources(self): - """Test that SWAP decomposes into three CNOTs""" - op = re.ResourceSWAP([0, 1]) - cnot = re.ResourceCNOT.resource_rep() - expected = {cnot: 3} - - assert op.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct""" - op = re.ResourceSWAP([0, 1]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test the compact representation""" - expected = re.CompressedResourceOp(re.ResourceSWAP, {}) - assert re.ResourceSWAP.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be computed from the compressed representation""" - - op = re.ResourceSWAP([0, 1]) - expected = {re.ResourceCNOT.resource_rep(): 3} - - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - def test_adjoint_decomp(self): - """Test that the adjoint decomposition is correct.""" - swap = re.ResourceSWAP([0, 1]) - swap_dag = re.ResourceAdjoint(re.ResourceSWAP([0, 1])) - - assert re.get_resources(swap) == re.get_resources(swap_dag) - - ctrl_data = ( - ( - ["c1"], - [1], - [], - { - re.ResourceCSWAP.resource_rep(): 1, - }, - ), - ( - ["c1"], - [0], - [], - { - re.ResourceCSWAP.resource_rep(): 1, - re.ResourceX.resource_rep(): 2, - }, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - { - re.ResourceCNOT.resource_rep(): 2, - re.ResourceMultiControlledX.resource_rep(2, 0, 1): 1, - }, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - { - re.ResourceCNOT.resource_rep(): 2, - re.ResourceMultiControlledX.resource_rep(3, 2, 2): 1, - }, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourceSWAP([0, 1]) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {re.ResourceSWAP.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (3, {re.ResourceSWAP.resource_rep(): 1}), - (4, {re.ResourceIdentity.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_pow_decomp(self, z, expected_res): - """Test that the pow decomposition is correct.""" - op = re.ResourceSWAP([0, 1]) - assert op.pow_resource_decomp(z) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res - - -class TestS: - """Tests for ResourceS""" - - def test_resources(self): - """Test that S decomposes into two Ts""" - op = re.ResourceS(0) - expected = {re.CompressedResourceOp(re.ResourceT, {}): 2} - assert op.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct""" - op = re.ResourceS(0) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct""" - expected = re.CompressedResourceOp(re.ResourceS, {}) - assert re.ResourceS.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be computed from the compressed representation""" - - op = re.ResourceS(0) - expected = {re.ResourceT.resource_rep(): 2} - - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - def test_adjoint_decomposition(self): - """Test that the adjoint resources are correct.""" - expected = {re.ResourceS.resource_rep(): 3} - assert re.ResourceS.adjoint_resource_decomp() == expected - - s = re.ResourceS(0) - s_dag = re.ResourceAdjoint(s) - - r1 = re.get_resources(s) * 3 - r2 = re.get_resources(s_dag) - assert r1 == r2 - - pow_data = ( - (1, {re.ResourceS.resource_rep(): 1}), - (2, {re.ResourceS.resource_rep(): 2}), - (3, {re.ResourceS.resource_rep(): 3}), - (4, {re.ResourceIdentity.resource_rep(): 1}), - (7, {re.ResourceS.resource_rep(): 3}), - (8, {re.ResourceIdentity.resource_rep(): 1}), - (14, {re.ResourceS.resource_rep(): 2}), - (15, {re.ResourceS.resource_rep(): 3}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_pow_decomp(self, z, expected_res): - """Test that the pow decomposition is correct.""" - op = re.ResourceS(0) - assert op.pow_resource_decomp(z) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res - - ctrl_data = ( - ( - ["c1"], - [1], - [], - { - re.ResourceControlledPhaseShift.resource_rep(): 1, - }, - ), - ( - ["c1"], - [0], - [], - { - re.ResourceControlledPhaseShift.resource_rep(): 1, - re.ResourceX.resource_rep(): 2, - }, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - { - re.ResourceControlledPhaseShift.resource_rep(): 1, - re.ResourceMultiControlledX.resource_rep(2, 0, 1): 2, - }, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - { - re.ResourceControlledPhaseShift.resource_rep(): 1, - re.ResourceMultiControlledX.resource_rep(3, 2, 2): 2, - }, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourceS(0) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - -class TestT: - """Tests for ResourceT""" - - def test_resources(self): - """Test that there is no further decomposition of the T gate.""" - op = re.ResourceT(0) - with pytest.raises(re.ResourcesNotDefined): - op.resources() - - def test_resource_params(self): - """Test that the resource params are correct""" - op = re.ResourceT(0) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compact representation is correct""" - expected = re.CompressedResourceOp(re.ResourceT, {}) - assert re.ResourceT.resource_rep() == expected - - def test_adjoint_decomposition(self): - """Test that the adjoint resources are correct.""" - expected = {re.ResourceT.resource_rep(): 7} - assert re.ResourceT.adjoint_resource_decomp() == expected - - t = re.ResourceT(0) - t_dag = re.ResourceAdjoint(t) - - r1 = re.get_resources(t) * 7 - r2 = re.get_resources(t_dag) - assert r1 == r2 - - ctrl_data = ( - ( - ["c1"], - [1], - [], - { - re.ResourceControlledPhaseShift.resource_rep(): 1, - }, - ), - ( - ["c1"], - [0], - [], - { - re.ResourceControlledPhaseShift.resource_rep(): 1, - re.ResourceX.resource_rep(): 2, - }, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - { - re.ResourceControlledPhaseShift.resource_rep(): 1, - re.ResourceMultiControlledX.resource_rep(2, 0, 1): 2, - }, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - { - re.ResourceControlledPhaseShift.resource_rep(): 1, - re.ResourceMultiControlledX.resource_rep(3, 2, 2): 2, - }, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourceT(0) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {re.ResourceT.resource_rep(): 1}), - (2, {re.ResourceT.resource_rep(): 2}), - (3, {re.ResourceT.resource_rep(): 3}), - (7, {re.ResourceT.resource_rep(): 7}), - (8, {re.ResourceIdentity.resource_rep(): 1}), - (14, {re.ResourceT.resource_rep(): 6}), - (15, {re.ResourceT.resource_rep(): 7}), - (16, {re.ResourceIdentity.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_pow_decomp(self, z, expected_res): - """Test that the pow decomposition is correct.""" - op = re.ResourceT(0) - assert op.pow_resource_decomp(z) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res - - -class TestX: - """Tests for the ResourceX gate""" - - def test_resources(self): - """Tests for the ResourceX gate""" - expected = { - re.ResourceS.resource_rep(): 2, - re.ResourceHadamard.resource_rep(): 2, - } - assert re.ResourceX.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct""" - op = re.ResourceX(0) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compact representation is correct""" - expected = re.CompressedResourceOp(re.ResourceX, {}) - assert re.ResourceX.resource_rep() == expected - - ctrl_data = ( - ( - ["c1"], - [1], - [], - { - re.ResourceCNOT.resource_rep(): 1, - }, - ), - ( - ["c1"], - [0], - [], - { - re.ResourceCNOT.resource_rep(): 1, - re.ResourceX.resource_rep(): 2, - }, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - { - re.ResourceToffoli.resource_rep(): 1, - }, - ), - ( - ["c1", "c2"], - [0, 0], - ["w1"], - { - re.ResourceToffoli.resource_rep(): 1, - re.ResourceX.resource_rep(): 4, - }, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - { - re.ResourceMultiControlledX.resource_rep(3, 2, 2): 1, - }, - ), - ( - ["c1", "c2", "c3", "c4"], - [1, 0, 0, 1], - ["w1", "w2"], - { - re.ResourceMultiControlledX.resource_rep(4, 2, 2): 1, - }, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourceX(0) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - def test_adjoint_decomposition(self): - """Test that the adjoint resources are correct.""" - expected = {re.ResourceX.resource_rep(): 1} - assert re.ResourceX.adjoint_resource_decomp() == expected - - x = re.ResourceX(0) - x_dag = re.ResourceAdjoint(x) - - r1 = re.get_resources(x) - r2 = re.get_resources(x_dag) - assert r1 == r2 - - pow_data = ( - (1, {re.ResourceX.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (3, {re.ResourceX.resource_rep(): 1}), - (4, {re.ResourceIdentity.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_pow_decomp(self, z, expected_res): - """Test that the pow decomposition is correct.""" - op = re.ResourceX(0) - assert op.pow_resource_decomp(z) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res - - -class TestY: - """Tests for the ResourceY gate""" - - def test_resources(self): - """Test that ResourceT does not implement a decomposition""" - expected = { - re.ResourceS.resource_rep(): 6, - re.ResourceHadamard.resource_rep(): 2, - } - assert re.ResourceY.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct""" - op = re.ResourceY(0) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compact representation is correct""" - expected = re.CompressedResourceOp(re.ResourceY, {}) - assert re.ResourceY.resource_rep() == expected - - ctrl_data = ( - ( - ["c1"], - [1], - [], - { - re.ResourceCY.resource_rep(): 1, - }, - ), - ( - ["c1"], - [0], - [], - { - re.ResourceCY.resource_rep(): 1, - re.ResourceX.resource_rep(): 2, - }, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - { - re.ResourceS.resource_rep(): 1, - re.ResourceAdjoint.resource_rep(re.ResourceS, {}): 1, - re.ResourceMultiControlledX.resource_rep(2, 0, 1): 1, - }, - ), - ( - ["c1", "c2"], - [0, 0], - ["w1"], - { - re.ResourceS.resource_rep(): 1, - re.ResourceAdjoint.resource_rep(re.ResourceS, {}): 1, - re.ResourceMultiControlledX.resource_rep(2, 2, 1): 1, - }, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - { - re.ResourceS.resource_rep(): 1, - re.ResourceAdjoint.resource_rep(re.ResourceS, {}): 1, - re.ResourceMultiControlledX.resource_rep(3, 2, 2): 1, - }, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourceY(0) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - def test_adjoint_decomposition(self): - """Test that the adjoint resources are correct.""" - expected = {re.ResourceY.resource_rep(): 1} - assert re.ResourceY.adjoint_resource_decomp() == expected - - y = re.ResourceY(0) - y_dag = re.ResourceAdjoint(y) - - r1 = re.get_resources(y) - r2 = re.get_resources(y_dag) - assert r1 == r2 - - pow_data = ( - (1, {re.ResourceY.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (3, {re.ResourceY.resource_rep(): 1}), - (4, {re.ResourceIdentity.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_pow_decomp(self, z, expected_res): - """Test that the pow decomposition is correct.""" - op = re.ResourceY(0) - assert op.pow_resource_decomp(z) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res - - -class TestZ: - """Tests for the ResourceZ gate""" - - def test_resources(self): - """Test that ResourceT does not implement a decomposition""" - expected = { - re.ResourceS.resource_rep(): 2, - } - assert re.ResourceZ.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct""" - op = re.ResourceZ(0) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compact representation is correct""" - expected = re.CompressedResourceOp(re.ResourceZ, {}) - assert re.ResourceZ.resource_rep() == expected - - ctrl_data = ( - ( - ["c1"], - [1], - [], - { - re.ResourceCZ.resource_rep(): 1, - }, - ), - ( - ["c1"], - [0], - [], - { - re.ResourceCZ.resource_rep(): 1, - re.ResourceX.resource_rep(): 2, - }, - ), - ( - ["c1", "c2"], - [1, 1], - ["w1"], - { - re.ResourceCCZ.resource_rep(): 1, - }, - ), - ( - ["c1", "c2"], - [0, 0], - ["w1"], - { - re.ResourceCCZ.resource_rep(): 1, - re.ResourceX.resource_rep(): 4, - }, - ), - ( - ["c1", "c2", "c3"], - [1, 0, 0], - ["w1", "w2"], - { - re.ResourceHadamard.resource_rep(): 2, - re.ResourceMultiControlledX.resource_rep(3, 2, 2): 1, - }, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", - ctrl_data, - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourceZ(0) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - def test_adjoint_decomposition(self): - """Test that the adjoint resources are correct.""" - expected = {re.ResourceZ.resource_rep(): 1} - assert re.ResourceZ.adjoint_resource_decomp() == expected - - z = re.ResourceZ(0) - z_dag = re.ResourceAdjoint(z) - - r1 = re.get_resources(z) - r2 = re.get_resources(z_dag) - assert r1 == r2 - - pow_data = ( - (1, {re.ResourceZ.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (3, {re.ResourceZ.resource_rep(): 1}), - (4, {re.ResourceIdentity.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_pow_decomp(self, z, expected_res): - """Test that the pow decomposition is correct.""" - op = re.ResourceZ(0) - assert op.pow_resource_decomp(z) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res diff --git a/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_multi_qubit.py b/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_multi_qubit.py deleted file mode 100644 index 24f37ec1e0e..00000000000 --- a/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_multi_qubit.py +++ /dev/null @@ -1,551 +0,0 @@ -# Copyright 2025 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. -"""Tests for parametric multi qubit resource operators.""" - -import pytest - -import pennylane.labs.resource_estimation as re - -# pylint: disable=use-implicit-booleaness-not-comparison,no-self-use,too-many-arguments - - -class TestMultiRZ: - """Test the ResourceMultiRZ class.""" - - @pytest.mark.parametrize("num_wires", range(1, 10)) - def test_resource_params(self, num_wires): - """Test that the resource params are correct.""" - op = re.ResourceMultiRZ(0.5, range(num_wires)) - assert op.resource_params == {"num_wires": num_wires} - - @pytest.mark.parametrize("num_wires", range(1, 10)) - def test_resource_rep(self, num_wires): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceMultiRZ, {"num_wires": num_wires}) - assert re.ResourceMultiRZ.resource_rep(num_wires) == expected - - @pytest.mark.parametrize("num_wires", range(1, 10)) - def test_resources(self, num_wires): - """Test that the resources are correct.""" - expected = { - re.ResourceCNOT.resource_rep(): 2 * (num_wires - 1), - re.ResourceRZ.resource_rep(): 1, - } - assert re.ResourceMultiRZ.resources(num_wires) == expected - - @pytest.mark.parametrize("num_wires", range(1, 10)) - def test_resources_from_rep(self, num_wires): - """Test that the resources can be computed from the compressed representation and params.""" - op = re.ResourceMultiRZ(0.5, wires=range(num_wires)) - expected = { - re.ResourceCNOT.resource_rep(): 2 * (num_wires - 1), - re.ResourceRZ.resource_rep(): 1, - } - - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - @pytest.mark.parametrize("num_wires", range(1, 5)) - def test_adjoint_decomp(self, num_wires): - """Test that the adjoint decomposition is correct.""" - expected = {re.ResourceMultiRZ.resource_rep(num_wires=num_wires): 1} - assert re.ResourceMultiRZ.adjoint_resource_decomp(num_wires=num_wires) == expected - - multi_rz = re.ResourceMultiRZ(0.123, wires=range(num_wires)) - multi_rz_dag = re.ResourceAdjoint(multi_rz) - - assert re.get_resources(multi_rz) == re.get_resources(multi_rz_dag) - - ctrl_data = ( - ( - [1], - [1], - [], - { - re.ResourceCNOT.resource_rep(): 4, - re.ResourceControlled.resource_rep(re.ResourceRZ, {}, 1, 0, 0): 1, - }, - ), - ( - [1], - [0], - [], - { - re.ResourceControlled.resource_rep( - re.ResourceMultiRZ, {"num_wires": 3}, 1, 0, 0 - ): 1, - re.ResourceX.resource_rep(): 2, - }, - ), - ( - [1, 2], - [1, 1], - ["w1"], - { - re.ResourceCNOT.resource_rep(): 4, - re.ResourceControlled.resource_rep(re.ResourceRZ, {}, 2, 0, 1): 1, - }, - ), - ( - [1, 2, 3], - [1, 0, 0], - ["w1", "w2"], - { - re.ResourceControlled.resource_rep( - re.ResourceMultiRZ, {"num_wires": 3}, 3, 0, 2 - ): 1, - re.ResourceX.resource_rep(): 4, - }, - ), - ) - - @pytest.mark.parametrize("ctrl_wires, ctrl_values, work_wires, expected_res", ctrl_data) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourceMultiRZ(1.24, wires=range(5, 8)) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - if num_ctrl_values != 0: - with pytest.raises(re.ResourcesNotDefined): - op.controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires, **op.resource_params - ) - else: - assert ( - op.controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires, **op.resource_params - ) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (1, {re.ResourceMultiRZ.resource_rep(num_wires=4): 1}), - (2, {re.ResourceMultiRZ.resource_rep(num_wires=4): 1}), - (3, {re.ResourceMultiRZ.resource_rep(num_wires=4): 1}), - (4, {re.ResourceMultiRZ.resource_rep(num_wires=4): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_pow_decomp(self, z, expected_res): - """Test that the pow decomposition is correct.""" - op = re.ResourceMultiRZ(1.23, wires=range(4)) - assert op.pow_resource_decomp(z, **op.resource_params) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res - - -class TestPauliRot: - """Test the ResourcePauliRot class.""" - - pauli_words = ("I", "XYZ", "XXX", "XIYIZIX", "III") - - @pytest.mark.parametrize("pauli_string", pauli_words) - def test_resource_params(self, pauli_string): - """Test that the resource params are correct.""" - op = re.ResourcePauliRot(theta=0.5, pauli_word=pauli_string, wires=range(len(pauli_string))) - assert op.resource_params == {"pauli_string": pauli_string} - - @pytest.mark.parametrize("pauli_string", pauli_words) - def test_resource_rep(self, pauli_string): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourcePauliRot, {"pauli_string": pauli_string}) - assert re.ResourcePauliRot.resource_rep(pauli_string) == expected - - expected_h_count = (0, 4, 6, 6, 0) - expected_s_count = (0, 1, 0, 1, 0) - params = zip(pauli_words, expected_h_count, expected_s_count) - - @pytest.mark.parametrize("pauli_string, expected_h_count, expected_s_count", params) - def test_resources(self, pauli_string, expected_h_count, expected_s_count): - """Test that the resources are correct.""" - active_wires = len(pauli_string.replace("I", "")) - - if set(pauli_string) == {"I"}: - expected = {re.ResourceGlobalPhase.resource_rep(): 1} - else: - expected = { - re.ResourceRZ.resource_rep(): 1, - re.ResourceCNOT.resource_rep(): 2 * (active_wires - 1), - } - - if expected_h_count: - expected[re.ResourceHadamard.resource_rep()] = expected_h_count - - if expected_s_count: - expected[re.ResourceS.resource_rep()] = expected_s_count - expected[re.ResourceAdjoint.resource_rep(re.ResourceS, {})] = expected_s_count - - assert re.ResourcePauliRot.resources(pauli_string) == expected - - def test_resources_empty_pauli_string(self): - """Test that the resources method produces the correct result for an empty pauli string.""" - expected = {re.ResourceGlobalPhase.resource_rep(): 1} - assert re.ResourcePauliRot.resources(pauli_string="") == expected - - @pytest.mark.parametrize("pauli_string, expected_h_count, expected_s_count", params) - def test_resources_from_rep(self, pauli_string, expected_h_count, expected_s_count): - """Test that the resources can be computed from the compressed representation and params.""" - op = re.ResourcePauliRot(0.5, pauli_string, wires=range(len(pauli_string))) - active_wires = len(pauli_string.replace("I", "")) - - if (set(pauli_string) == {"I"}) or (pauli_string == ""): - expected = {re.ResourceGlobalPhase.resource_rep(): 1} - - else: - expected = { - re.ResourceRZ.resource_rep(): 1, - re.ResourceCNOT.resource_rep(): 2 * (active_wires - 1), - } - - if expected_h_count: - expected[re.ResourceHadamard.resource_rep()] = expected_h_count - - if expected_s_count: - expected[re.ResourceS.resource_rep()] = expected_s_count - expected[re.ResourceAdjoint.resource_rep(re.ResourceS, {})] = expected_s_count - - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - @pytest.mark.parametrize("pauli_word", pauli_words) - def test_adjoint_decomp(self, pauli_word): - """Test that the adjoint decomposition is correct.""" - expected = {re.ResourcePauliRot.resource_rep(pauli_string=pauli_word): 1} - assert re.ResourcePauliRot.adjoint_resource_decomp(pauli_string=pauli_word) == expected - - op = re.ResourcePauliRot(theta=0.5, pauli_word=pauli_word, wires=range(len(pauli_word))) - op_dag = re.ResourceAdjoint(op) - - assert re.get_resources(op) == re.get_resources(op_dag) - - ctrl_data = ( - ( - "XXX", - [1], - [1], - [], - { - re.ResourceHadamard.resource_rep(): 6, - re.ResourceControlled.resource_rep(re.ResourceRZ, {}, 1, 0, 0): 1, - re.ResourceCNOT.resource_rep(): 4, - }, - ), - ( - "XXX", - [1], - [0], - [], - { - re.ResourceHadamard.resource_rep(): 6, - re.ResourceControlled.resource_rep(re.ResourceRZ, {}, 1, 1, 0): 1, - re.ResourceCNOT.resource_rep(): 4, - }, - ), - ( - "XXX", - [1, 2], - [1, 1], - ["w1"], - { - re.ResourceHadamard.resource_rep(): 6, - re.ResourceControlled.resource_rep(re.ResourceRZ, {}, 2, 0, 1): 1, - re.ResourceCNOT.resource_rep(): 4, - }, - ), - ( - "XIYIZIX", - [1], - [1], - [], - { - re.ResourceHadamard.resource_rep(): 6, - re.ResourceS.resource_rep(): 1, - re.ResourceAdjoint.resource_rep(re.ResourceS, {}): 1, - re.ResourceControlled.resource_rep(re.ResourceRZ, {}, 1, 0, 0): 1, - re.ResourceCNOT.resource_rep(): 6, - }, - ), - ( - "XIYIZIX", - [1], - [0], - [], - { - re.ResourceHadamard.resource_rep(): 6, - re.ResourceS.resource_rep(): 1, - re.ResourceAdjoint.resource_rep(re.ResourceS, {}): 1, - re.ResourceControlled.resource_rep(re.ResourceRZ, {}, 1, 1, 0): 1, - re.ResourceCNOT.resource_rep(): 6, - }, - ), - ( - "XIYIZIX", - [1, 2], - [1, 1], - ["w1"], - { - re.ResourceHadamard.resource_rep(): 6, - re.ResourceS.resource_rep(): 1, - re.ResourceAdjoint.resource_rep(re.ResourceS, {}): 1, - re.ResourceControlled.resource_rep(re.ResourceRZ, {}, 2, 0, 1): 1, - re.ResourceCNOT.resource_rep(): 6, - }, - ), - ( - "III", - [1], - [1], - [], - {re.ResourceControlled.resource_rep(re.ResourceGlobalPhase, {}, 1, 0, 0): 1}, - ), - ( - "X", - [1], - [0], - [], - {re.ResourceControlled.resource_rep(re.ResourceRX, {}, 1, 1, 0): 1}, - ), - ( - "Y", - [1, 2], - [1, 1], - ["w1"], - {re.ResourceControlled.resource_rep(re.ResourceRY, {}, 2, 0, 1): 1}, - ), - ) - - @pytest.mark.parametrize( - "pauli_word, ctrl_wires, ctrl_values, work_wires, expected_res", ctrl_data - ) - def test_resource_controlled( - self, ctrl_wires, ctrl_values, work_wires, pauli_word, expected_res - ): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourcePauliRot( - 1.24, pauli_word, wires=list(f"wire_{i}" for i in range(len(pauli_word))) - ) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires, **op.resource_params - ) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - @pytest.mark.parametrize("z", range(1, 5)) - @pytest.mark.parametrize("pauli_word", pauli_words) - def test_pow_decomp(self, z, pauli_word): - """Test that the pow decomposition is correct.""" - op = re.ResourcePauliRot(theta=0.5, pauli_word=pauli_word, wires=range(len(pauli_word))) - expected_res = {re.ResourcePauliRot.resource_rep(pauli_string=pauli_word): 1} - assert op.pow_resource_decomp(z, **op.resource_params) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res - - -class TestIsingXX: - """Test the IsingXX class.""" - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceIsingXX(0.5, wires=[0, 1]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceIsingXX, {}) - assert re.ResourceIsingXX.resource_rep() == expected - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceCNOT.resource_rep(): 2, - re.ResourceRX.resource_rep(): 1, - } - assert re.ResourceIsingXX.resources() == expected - - def test_resources_from_rep(self): - """Test that the resources can be computed from the compressed representation and params.""" - op = re.ResourceIsingXX(0.5, wires=[0, 1]) - expected = { - re.ResourceCNOT.resource_rep(): 2, - re.ResourceRX.resource_rep(): 1, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - -class TestIsingXY: - """Test the IsingXY class.""" - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceIsingXY(0.5, wires=[0, 1]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceIsingXY, {}) - assert re.ResourceIsingXY.resource_rep() == expected - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceHadamard.resource_rep(): 2, - re.ResourceCY.resource_rep(): 2, - re.ResourceRY.resource_rep(): 1, - re.ResourceRX.resource_rep(): 1, - } - assert re.ResourceIsingXY.resources() == expected - - def test_resources_from_rep(self): - """Test that the resources can be computed from the compressed representation and params.""" - op = re.ResourceIsingXY(0.5, wires=[0, 1]) - expected = { - re.ResourceHadamard.resource_rep(): 2, - re.ResourceCY.resource_rep(): 2, - re.ResourceRY.resource_rep(): 1, - re.ResourceRX.resource_rep(): 1, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - -class TestIsingYY: - """Test the IsingYY class.""" - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceIsingYY(0.5, wires=[0, 1]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceIsingYY, {}) - assert re.ResourceIsingYY.resource_rep() == expected - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceCY.resource_rep(): 2, - re.ResourceRY.resource_rep(): 1, - } - assert re.ResourceIsingYY.resources() == expected - - def test_resources_from_rep(self): - """Test that the resources can be computed from the compressed representation and params.""" - op = re.ResourceIsingYY(0.5, wires=[0, 1]) - expected = { - re.ResourceCY.resource_rep(): 2, - re.ResourceRY.resource_rep(): 1, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - -class TestIsingZZ: - """Test the IsingZZ class.""" - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceIsingZZ(0.5, wires=[0, 1]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceIsingZZ, {}) - assert re.ResourceIsingZZ.resource_rep() == expected - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceCNOT.resource_rep(): 2, - re.ResourceRZ.resource_rep(): 1, - } - assert re.ResourceIsingZZ.resources() == expected - - def test_resources_from_rep(self): - """Test that the resources can be computed from the compressed representation and params.""" - op = re.ResourceIsingZZ(0.5, wires=[0, 1]) - expected = { - re.ResourceCNOT.resource_rep(): 2, - re.ResourceRZ.resource_rep(): 1, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - -class TestPSWAP: - """Test the PSWAP class.""" - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourcePSWAP(0.5, wires=[0, 1]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourcePSWAP, {}) - assert re.ResourcePSWAP.resource_rep() == expected - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceSWAP.resource_rep(): 1, - re.ResourceCNOT.resource_rep(): 2, - re.ResourcePhaseShift.resource_rep(): 1, - } - assert re.ResourcePSWAP.resources() == expected - - def test_resources_from_rep(self): - """Test that the resources can be computed from the compressed representation and params.""" - op = re.ResourcePSWAP(0.5, wires=[0, 1]) - expected = { - re.ResourceSWAP.resource_rep(): 1, - re.ResourceCNOT.resource_rep(): 2, - re.ResourcePhaseShift.resource_rep(): 1, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected diff --git a/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_single_qubit.py b/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_single_qubit.py deleted file mode 100644 index 64bfe0d3d7c..00000000000 --- a/pennylane/labs/tests/resource_estimation/ops/qubit/test_parametric_ops_single_qubit.py +++ /dev/null @@ -1,460 +0,0 @@ -# Copyright 2025 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. -""" -Tests for parametric single qubit resource operators. -""" -import copy - -import pytest - -import pennylane.labs.resource_estimation as re -from pennylane.labs.resource_estimation.ops.qubit.parametric_ops_single_qubit import ( - _rotation_resources, -) - -# pylint: disable=no-self-use, use-implicit-booleaness-not-comparison,too-many-arguments - -params = list(zip([10e-3, 10e-4, 10e-5], [17, 21, 24])) - - -@pytest.mark.parametrize("epsilon, expected", params) -def test_rotation_resources(epsilon, expected): - """Test the hardcoded resources used for RX, RY, RZ""" - gate_types = {} - - t = re.CompressedResourceOp(re.ResourceT, {}) - gate_types[t] = expected - assert gate_types == _rotation_resources(epsilon=epsilon) - - -class TestPauliRotation: - """Test ResourceRX, ResourceRY, and ResourceRZ""" - - params_classes = [re.ResourceRX, re.ResourceRY, re.ResourceRZ] - params_errors = [10e-3, 10e-4, 10e-5] - params_ctrl_res = [ - { - re.ResourceHadamard.resource_rep(): 2, - re.ResourceRZ.resource_rep(): 2, - }, - { - re.ResourceRY.resource_rep(): 2, - }, - { - re.ResourceRZ.resource_rep(): 2, - }, - ] - - @pytest.mark.parametrize("resource_class", params_classes) - @pytest.mark.parametrize("epsilon", params_errors) - def test_resources(self, resource_class, epsilon): - """Test the resources method""" - - label = "error_" + resource_class.__name__.replace("Resource", "").lower() - config = {label: epsilon} - op = resource_class(1.24, wires=0) - assert op.resources(config) == _rotation_resources(epsilon=epsilon) - - @pytest.mark.parametrize("resource_class", params_classes) - @pytest.mark.parametrize("epsilon", params_errors) - def test_resource_rep(self, resource_class, epsilon): # pylint: disable=unused-argument - """Test the compact representation""" - op = resource_class(1.24, wires=0) - expected = re.CompressedResourceOp(resource_class, {}) - assert op.resource_rep() == expected - - @pytest.mark.parametrize("resource_class", params_classes) - @pytest.mark.parametrize("epsilon", params_errors) - def test_resources_from_rep(self, resource_class, epsilon): - """Test the resources can be obtained from the compact representation""" - - label = "error_" + resource_class.__name__.replace("Resource", "").lower() - config = {label: epsilon} - op = resource_class(1.24, wires=0) - expected = _rotation_resources(epsilon=epsilon) - - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params, config=config) == expected - - @pytest.mark.parametrize("resource_class", params_classes) - @pytest.mark.parametrize("epsilon", params_errors) - def test_resource_params(self, resource_class, epsilon): # pylint: disable=unused-argument - """Test that the resource params are correct""" - op = resource_class(1.24, wires=0) - assert op.resource_params == {} - - @pytest.mark.parametrize("resource_class", params_classes) - @pytest.mark.parametrize("epsilon", params_errors) - def test_adjoint_decomposition(self, resource_class, epsilon): - """Test that the adjoint decompositions are correct.""" - - expected = {resource_class.resource_rep(): 1} - assert resource_class.adjoint_resource_decomp() == expected - - op = resource_class(1.24, wires=0) - dag = re.ResourceAdjoint(op) - - label = "error_" + resource_class.__name__.replace("Resource", "").lower() - config = {label: epsilon} - - r1 = re.get_resources(op, config=config) - r2 = re.get_resources(dag, config=config) - - assert r1 == r2 - - @pytest.mark.parametrize("resource_class", params_classes) - @pytest.mark.parametrize("epsilon", params_errors) - @pytest.mark.parametrize("z", list(range(0, 10))) - def test_pow_decomposition(self, resource_class, epsilon, z): - """Test that the pow decompositions are correct.""" - - expected = ( - {resource_class.resource_rep(): 1} if z else {re.ResourceIdentity.resource_rep(): 1} - ) - assert resource_class.pow_resource_decomp(z) == expected - - op = resource_class(1.24, wires=0) if z else re.ResourceIdentity(wires=0) - dag = re.ResourcePow(op, z) - - label = "error_" + resource_class.__name__.replace("Resource", "").lower() - config = {label: epsilon} - - r1 = re.get_resources(op, config=config) - r2 = re.get_resources(dag, config=config) - - assert r1 == r2 - - params_ctrl_classes = ( - (re.ResourceRX, re.ResourceCRX), - (re.ResourceRY, re.ResourceCRY), - (re.ResourceRZ, re.ResourceCRZ), - ) - - @pytest.mark.parametrize("resource_class, controlled_class", params_ctrl_classes) - @pytest.mark.parametrize("epsilon", params_errors) - def test_controlled_decomposition_single_control( - self, resource_class, controlled_class, epsilon - ): - """Test that the controlled decompositions are correct.""" - expected = {controlled_class.resource_rep(): 1} - assert resource_class.controlled_resource_decomp(1, 0, 0) == expected - - expected = {controlled_class.resource_rep(): 1, re.ResourceX.resource_rep(): 2} - assert resource_class.controlled_resource_decomp(1, 1, 0) == expected - - op = resource_class(1.24, wires=0) - c_op = re.ResourceControlled(op, control_wires=[1]) - - c = controlled_class(1.24, wires=[0, 1]) - - config = {"error_rx": epsilon, "error_ry": epsilon, "error_rz": epsilon} - - r1 = re.get_resources(c, config=config) - r2 = re.get_resources(c_op, config=config) - - assert r1 == r2 - - ctrl_res_data = ( - ( - [1, 2], - [1, 1], - ["w1"], - {re.ResourceMultiControlledX.resource_rep(2, 0, 1): 2}, - ), - ( - [1, 2], - [1, 0], - [], - {re.ResourceMultiControlledX.resource_rep(2, 1, 0): 2}, - ), - ( - [1, 2, 3], - [1, 0, 0], - ["w1", "w2"], - {re.ResourceMultiControlledX.resource_rep(3, 2, 2): 2}, - ), - ) - - @pytest.mark.parametrize("resource_class, local_res", zip(params_classes, params_ctrl_res)) - @pytest.mark.parametrize("ctrl_wires, ctrl_values, work_wires, general_res", ctrl_res_data) - def test_controlled_decomposition_multi_controlled( - self, resource_class, local_res, ctrl_wires, ctrl_values, work_wires, general_res - ): - """Test that the controlled docomposition is correct when controlled on multiple wires.""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = resource_class(1.23, wires=0) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - expected_resources = copy.copy(local_res) - for k, v in general_res.items(): - expected_resources[k] = v - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_resources - ) - assert op2.resources(**op2.resource_params) == expected_resources - - # pylint: disable=unused-argument, import-outside-toplevel - @pytest.mark.parametrize("resource_class", params_classes) - @pytest.mark.parametrize("epsilon", params_errors) - def test_sparse_matrix_format(self, resource_class, epsilon): - """Test that the sparse matrix accepts the format parameter.""" - from scipy.sparse import coo_matrix, csc_matrix, csr_matrix, lil_matrix - - op = resource_class(1.24, wires=0) - assert isinstance(op.sparse_matrix(), csr_matrix) - assert isinstance(op.sparse_matrix(format="csc"), csc_matrix) - assert isinstance(op.sparse_matrix(format="lil"), lil_matrix) - assert isinstance(op.sparse_matrix(format="coo"), coo_matrix) - - -class TestRot: - """Test ResourceRot""" - - def test_resources(self): - """Test the resources method""" - op = re.ResourceRot(0.1, 0.2, 0.3, wires=0) - ry = re.ResourceRY.resource_rep() - rz = re.ResourceRZ.resource_rep() - expected = {ry: 1, rz: 2} - - assert op.resources() == expected - - def test_resource_rep(self): - """Test the compressed representation""" - op = re.ResourceRot(0.1, 0.2, 0.3, wires=0) - expected = re.CompressedResourceOp(re.ResourceRot, {}) - assert op.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be obtained from the compact representation""" - op = re.ResourceRot(0.1, 0.2, 0.3, wires=0) - ry = re.ResourceRY.resource_rep() - rz = re.ResourceRZ.resource_rep() - expected = {ry: 1, rz: 2} - - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - def test_resource_params(self): - """Test that the resource params are correct""" - op = re.ResourceRot(0.1, 0.2, 0.3, wires=0) - assert op.resource_params == {} - - def test_adjoint_decomp(self): - """Test that the adjoint decomposition is correct""" - - expected = {re.ResourceRot.resource_rep(): 1} - assert re.ResourceRot.adjoint_resource_decomp() == expected - - op = re.ResourceRot(1.24, 1.25, 1.26, wires=0) - dag = re.ResourceAdjoint(op) - - r1 = re.get_resources(op) - r2 = re.get_resources(dag) - - assert r1 == r2 - - ctrl_data = ( - ([1], [1], [], {re.ResourceCRot.resource_rep(): 1}), - ( - [1], - [0], - [], - { - re.ResourceCRot.resource_rep(): 1, - re.ResourceX.resource_rep(): 2, - }, - ), - ( - [1, 2], - [1, 1], - ["w1"], - { - re.ResourceRZ.resource_rep(): 3, - re.ResourceRY.resource_rep(): 2, - re.ResourceMultiControlledX.resource_rep(2, 0, 1): 2, - }, - ), - ( - [1, 2, 3], - [1, 0, 0], - ["w1", "w2"], - { - re.ResourceRZ.resource_rep(): 3, - re.ResourceRY.resource_rep(): 2, - re.ResourceMultiControlledX.resource_rep(3, 2, 2): 2, - }, - ), - ) - - @pytest.mark.parametrize("ctrl_wires, ctrl_values, work_wires, expected_res", ctrl_data) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourceRot(1.24, 1.25, 1.26, wires=0) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (0, {re.ResourceIdentity.resource_rep(): 1}), - (1, {re.ResourceRot.resource_rep(): 1}), - (2, {re.ResourceRot.resource_rep(): 1}), - (5, {re.ResourceRot.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op = re.ResourceRot(1.24, 1.25, 1.26, wires=0) - assert op.pow_resource_decomp(z) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res - - -class TestPhaseShift: - """Test ResourcePhaseShift""" - - def test_resources(self): - """Test the resources method""" - op = re.ResourcePhaseShift(0.1, wires=0) - rz = re.ResourceRZ.resource_rep() - global_phase = re.ResourceGlobalPhase.resource_rep() - - expected = {rz: 1, global_phase: 1} - - assert op.resources() == expected - - def test_resource_rep(self): - """Test the compressed representation""" - op = re.ResourcePhaseShift(0.1, wires=0) - expected = re.CompressedResourceOp(re.ResourcePhaseShift, {}) - assert op.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be obtained from the compact representation""" - op = re.ResourcePhaseShift(0.1, wires=0) - global_phase = re.ResourceGlobalPhase.resource_rep() - rz = re.ResourceRZ.resource_rep() - expected = {global_phase: 1, rz: 1} - - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - def test_resource_params(self): - """Test that the resource params are correct""" - op = re.ResourcePhaseShift(0.1, wires=0) - assert op.resource_params == {} - - def test_adjoint_decomp(self): - """Test that the adjoint decomposition is correct""" - - expected = {re.ResourcePhaseShift.resource_rep(): 1} - assert re.ResourcePhaseShift.adjoint_resource_decomp() == expected - - op = re.ResourcePhaseShift(0.1, wires=0) - dag = re.ResourceAdjoint(op) - - r1 = re.get_resources(op) - r2 = re.get_resources(dag) - - assert r1 == r2 - - ctrl_data = ( - ([1], [1], [], {re.ResourceControlledPhaseShift.resource_rep(): 1}), - ( - [1], - [0], - [], - { - re.ResourceControlledPhaseShift.resource_rep(): 1, - re.ResourceX.resource_rep(): 2, - }, - ), - ( - [1, 2], - [1, 1], - ["w1"], - { - re.ResourceControlledPhaseShift.resource_rep(): 1, - re.ResourceMultiControlledX.resource_rep(2, 0, 1): 2, - }, - ), - ( - [1, 2, 3], - [1, 0, 0], - ["w1", "w2"], - { - re.ResourceControlledPhaseShift.resource_rep(): 1, - re.ResourceMultiControlledX.resource_rep(3, 2, 2): 2, - }, - ), - ) - - @pytest.mark.parametrize("ctrl_wires, ctrl_values, work_wires, expected_res", ctrl_data) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourcePhaseShift(0.1, wires=0) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - pow_data = ( - (0, {re.ResourceIdentity.resource_rep(): 1}), - (1, {re.ResourcePhaseShift.resource_rep(): 1}), - (2, {re.ResourcePhaseShift.resource_rep(): 1}), - (5, {re.ResourcePhaseShift.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op = re.ResourcePhaseShift(0.1, wires=0) - assert op.pow_resource_decomp(z) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res diff --git a/pennylane/labs/tests/resource_estimation/ops/qubit/test_qchem_ops.py b/pennylane/labs/tests/resource_estimation/ops/qubit/test_qchem_ops.py deleted file mode 100644 index 364502f2056..00000000000 --- a/pennylane/labs/tests/resource_estimation/ops/qubit/test_qchem_ops.py +++ /dev/null @@ -1,326 +0,0 @@ -# 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. -"""Tests for qchem ops resource operators.""" - -import pennylane.labs.resource_estimation as re - -# pylint: disable=use-implicit-booleaness-not-comparison,no-self-use - - -class TestSingleExcitation: - """Tests for the ResourceSingleExcitation class.""" - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceAdjoint.resource_rep(re.ResourceT, {}): 2, - re.ResourceHadamard.resource_rep(): 4, - re.ResourceS.resource_rep(): 2, - re.ResourceAdjoint.resource_rep(re.ResourceS, {}): 2, - re.ResourceCNOT.resource_rep(): 2, - re.ResourceRZ.resource_rep(): 1, - re.ResourceRY.resource_rep(): 1, - re.ResourceT.resource_rep(): 2, - } - assert re.ResourceSingleExcitation.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceSingleExcitation(0.5, wires=[0, 1]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceSingleExcitation, {}) - assert re.ResourceSingleExcitation.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be obtained from the compressed representation.""" - op = re.ResourceSingleExcitation(0.5, wires=[0, 1]) - expected = { - re.ResourceAdjoint.resource_rep(re.ResourceT, {}): 2, - re.ResourceHadamard.resource_rep(): 4, - re.ResourceS.resource_rep(): 2, - re.ResourceAdjoint.resource_rep(re.ResourceS, {}): 2, - re.ResourceCNOT.resource_rep(): 2, - re.ResourceRZ.resource_rep(): 1, - re.ResourceRY.resource_rep(): 1, - re.ResourceT.resource_rep(): 2, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - -class TestSingleExcitationMinus: - """Tests for the ResourceSingleExcitationMinus class.""" - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceX.resource_rep(): 4, - re.ResourceControlledPhaseShift.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 2, - re.ResourceCRY.resource_rep(): 1, - } - assert re.ResourceSingleExcitationMinus.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceSingleExcitationMinus(0.5, wires=[0, 1]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceSingleExcitationMinus, {}) - assert re.ResourceSingleExcitationMinus.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be obtained from the compressed representation.""" - op = re.ResourceSingleExcitationMinus(0.5, wires=[0, 1]) - expected = { - re.ResourceX.resource_rep(): 4, - re.ResourceControlledPhaseShift.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 2, - re.ResourceCRY.resource_rep(): 1, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - -class TestSingleExcitationPlus: - """Tests for the ResourceSingleExcitationPlus class.""" - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceX.resource_rep(): 4, - re.ResourceControlledPhaseShift.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 2, - re.ResourceCRY.resource_rep(): 1, - } - assert re.ResourceSingleExcitationPlus.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceSingleExcitationPlus(0.5, wires=[0, 1]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceSingleExcitationPlus, {}) - assert re.ResourceSingleExcitationPlus.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be obtained from the compressed representation.""" - op = re.ResourceSingleExcitationPlus(0.5, wires=[0, 1]) - expected = { - re.ResourceX.resource_rep(): 4, - re.ResourceControlledPhaseShift.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 2, - re.ResourceCRY.resource_rep(): 1, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - -class TestDoubleExcitation: - """Tests for the ResourceDoubleExcitation class.""" - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceHadamard.resource_rep(): 6, - re.ResourceRY.resource_rep(): 8, - re.ResourceCNOT.resource_rep(): 14, - } - assert re.ResourceDoubleExcitation.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceDoubleExcitation(0.5, wires=[0, 1, 2, 3]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceDoubleExcitation, {}) - assert re.ResourceDoubleExcitation.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be obtained from the compressed representation.""" - op = re.ResourceDoubleExcitation(0.5, wires=[0, 1, 2, 3]) - expected = { - re.ResourceHadamard.resource_rep(): 6, - re.ResourceRY.resource_rep(): 8, - re.ResourceCNOT.resource_rep(): 14, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - -class TestDoubleExcitationMinus: - """Tests for the ResourceDoubleExcitationMinus class.""" - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceGlobalPhase.resource_rep(): 1, - re.ResourceDoubleExcitation.resource_rep(): 1, - re.ResourceControlled.resource_rep(re.ResourceZ, {}, 3, 1, 0): 2, - re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 3, 1, 0): 2, - } - assert re.ResourceDoubleExcitationMinus.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceDoubleExcitationMinus(0.5, wires=[0, 1, 2, 3]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceDoubleExcitationMinus, {}) - assert re.ResourceDoubleExcitationMinus.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be obtained from the compressed representation.""" - op = re.ResourceDoubleExcitationMinus(0.5, wires=[0, 1, 2, 3]) - expected = { - re.ResourceGlobalPhase.resource_rep(): 1, - re.ResourceDoubleExcitation.resource_rep(): 1, - re.ResourceControlled.resource_rep(re.ResourceZ, {}, 3, 1, 0): 2, - re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 3, 1, 0): 2, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - -class TestDoubleExcitationPlus: - """Tests for the ResourceDoubleExcitationPlus class.""" - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceGlobalPhase.resource_rep(): 1, - re.ResourceDoubleExcitation.resource_rep(): 1, - re.ResourceControlled.resource_rep(re.ResourceZ, {}, 3, 1, 0): 2, - re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 3, 1, 0): 2, - } - assert re.ResourceDoubleExcitationPlus.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceDoubleExcitationPlus(0.5, wires=[0, 1, 3, 4]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceDoubleExcitationPlus, {}) - assert re.ResourceDoubleExcitationPlus.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be obtained from the compressed representation.""" - op = re.ResourceDoubleExcitationPlus(0.5, wires=[0, 1, 3, 4]) - expected = { - re.ResourceGlobalPhase.resource_rep(): 1, - re.ResourceDoubleExcitation.resource_rep(): 1, - re.ResourceControlled.resource_rep(re.ResourceZ, {}, 3, 1, 0): 2, - re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 3, 1, 0): 2, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - -class TestOrbitalRotation: - """Tests for the ResourceOrbitalRotation class.""" - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceFermionicSWAP.resource_rep(): 2, - re.ResourceSingleExcitation.resource_rep(): 2, - } - assert re.ResourceOrbitalRotation.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceOrbitalRotation(0.5, wires=[0, 1, 3, 4]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceOrbitalRotation, {}) - assert re.ResourceOrbitalRotation.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be obtained from the compressed representation.""" - op = re.ResourceOrbitalRotation(0.5, wires=[0, 1, 3, 4]) - expected = { - re.ResourceFermionicSWAP.resource_rep(): 2, - re.ResourceSingleExcitation.resource_rep(): 2, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - -class TestFermionicSWAP: - """Tests for the ResourceFermionicSWAP class.""" - - def test_resources(self): - """Test that the resources are correct.""" - expected = { - re.ResourceHadamard.resource_rep(): 4, - re.ResourceMultiRZ.resource_rep(num_wires=2): 2, - re.ResourceRX.resource_rep(): 4, - re.ResourceRZ.resource_rep(): 2, - re.ResourceGlobalPhase.resource_rep(): 1, - } - assert re.ResourceFermionicSWAP.resources() == expected - - def test_resource_params(self): - """Test that the resource params are correct.""" - op = re.ResourceFermionicSWAP(0.5, wires=[0, 1]) - assert op.resource_params == {} - - def test_resource_rep(self): - """Test that the compressed representation is correct.""" - expected = re.CompressedResourceOp(re.ResourceFermionicSWAP, {}) - assert re.ResourceFermionicSWAP.resource_rep() == expected - - def test_resources_from_rep(self): - """Test that the resources can be obtained from the compressed representation.""" - op = re.ResourceFermionicSWAP(0.5, wires=[0, 1]) - expected = { - re.ResourceHadamard.resource_rep(): 4, - re.ResourceMultiRZ.resource_rep(num_wires=2): 2, - re.ResourceRX.resource_rep(): 4, - re.ResourceRZ.resource_rep(): 2, - re.ResourceGlobalPhase.resource_rep(): 1, - } - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected diff --git a/pennylane/labs/tests/resource_estimation/ops/test_identity.py b/pennylane/labs/tests/resource_estimation/ops/test_identity.py deleted file mode 100644 index 0cfb385c429..00000000000 --- a/pennylane/labs/tests/resource_estimation/ops/test_identity.py +++ /dev/null @@ -1,191 +0,0 @@ -# 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. -""" -Tests for identity resource operators -""" -import pytest - -import pennylane.labs.resource_estimation as re - -# pylint: disable=no-self-use,use-implicit-booleaness-not-comparison - - -class TestIdentity: - """Test ResourceIdentity""" - - def test_resources(self): - """ResourceIdentity should have empty resources""" - op = re.ResourceIdentity() - assert op.resources() == {} - - def test_resource_rep(self): - """Test the compressed representation""" - expected = re.CompressedResourceOp(re.ResourceIdentity, {}) - assert re.ResourceIdentity.resource_rep() == expected - - def test_resource_params(self): - """Test the resource params are correct""" - op = re.ResourceIdentity(0) - assert op.resource_params == {} - - def test_resources_from_rep(self): - """Test that the resources can be computed from the compressed representation""" - op = re.ResourceIdentity() - expected = {} - - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - op = re.ResourceIdentity(0) - op2 = re.ResourceAdjoint(op) - assert op.adjoint_resource_decomp() == {re.ResourceIdentity.resource_rep(): 1} - assert op2.resources(**op2.resource_params) == {re.ResourceIdentity.resource_rep(): 1} - - identity_ctrl_data = ( - ([1], [1], [], {re.ResourceIdentity.resource_rep(): 1}), - ([1, 2], [1, 1], ["w1"], {re.ResourceIdentity.resource_rep(): 1}), - ([1, 2, 3], [1, 0, 0], ["w1", "w2"], {re.ResourceIdentity.resource_rep(): 1}), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", identity_ctrl_data - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_work_wires = len(work_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - - op = re.ResourceIdentity(0) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - identity_pow_data = ( - (1, {re.ResourceIdentity.resource_rep(): 1}), - (2, {re.ResourceIdentity.resource_rep(): 1}), - (5, {re.ResourceIdentity.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", identity_pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op = re.ResourceIdentity(0) - assert op.pow_resource_decomp(z) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res - - -class TestGlobalPhase: - """Test ResourceGlobalPhase""" - - def test_resources(self): - """ResourceGlobalPhase should have empty resources""" - op = re.ResourceGlobalPhase(0.1, wires=0) - assert op.resources() == {} - - def test_resource_rep(self): - """Test the compressed representation""" - expected = re.CompressedResourceOp(re.ResourceGlobalPhase, {}) - assert re.ResourceGlobalPhase.resource_rep() == expected - - def test_resource_params(self): - """Test the resource params are correct""" - op = re.ResourceGlobalPhase(0.1, wires=0) - assert op.resource_params == {} - - def test_resources_from_rep(self): - """Test that the resources can be computed from the compressed representation""" - op = re.ResourceGlobalPhase(0.1, wires=0) - expected = {} - - op_compressed_rep = op.resource_rep_from_op() - op_resource_type = op_compressed_rep.op_type - op_resource_params = op_compressed_rep.params - assert op_resource_type.resources(**op_resource_params) == expected - - def test_resource_adjoint(self): - """Test that the adjoint resources are as expected""" - op = re.ResourceGlobalPhase(0.1, wires=0) - op2 = re.ResourceAdjoint(op) - assert op.adjoint_resource_decomp() == {re.ResourceGlobalPhase.resource_rep(): 1} - assert op2.resources(**op2.resource_params) == {re.ResourceGlobalPhase.resource_rep(): 1} - - globalphase_ctrl_data = ( - ([1], [1], [], {re.ResourcePhaseShift.resource_rep(): 1}), - ( - [1, 2], - [1, 1], - ["w1"], - { - re.ResourcePhaseShift.resource_rep(): 1, - re.ResourceMultiControlledX.resource_rep(2, 0, 1): 2, - }, - ), - ( - [1, 2, 3], - [1, 0, 0], - ["w1", "w2"], - { - re.ResourcePhaseShift.resource_rep(): 1, - re.ResourceMultiControlledX.resource_rep(3, 2, 2): 2, - }, - ), - ) - - @pytest.mark.parametrize( - "ctrl_wires, ctrl_values, work_wires, expected_res", globalphase_ctrl_data - ) - def test_resource_controlled(self, ctrl_wires, ctrl_values, work_wires, expected_res): - """Test that the controlled resources are as expected""" - num_ctrl_wires = len(ctrl_wires) - num_ctrl_values = len([v for v in ctrl_values if not v]) - num_work_wires = len(work_wires) - - op = re.ResourceGlobalPhase(0.1, wires=0) - op2 = re.ResourceControlled( - op, control_wires=ctrl_wires, control_values=ctrl_values, work_wires=work_wires - ) - - assert ( - op.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires) - == expected_res - ) - assert op2.resources(**op2.resource_params) == expected_res - - globalphase_pow_data = ( - (1, {re.ResourceGlobalPhase.resource_rep(): 1}), - (2, {re.ResourceGlobalPhase.resource_rep(): 1}), - (5, {re.ResourceGlobalPhase.resource_rep(): 1}), - ) - - @pytest.mark.parametrize("z, expected_res", globalphase_pow_data) - def test_resource_pow(self, z, expected_res): - """Test that the pow resources are as expected""" - op = re.ResourceGlobalPhase(0.1, wires=0) - assert op.pow_resource_decomp(z) == expected_res - - op2 = re.ResourcePow(op, z) - assert op2.resources(**op2.resource_params) == expected_res diff --git a/pennylane/labs/tests/resource_estimation/templates/test_resource_basisrotation.py b/pennylane/labs/tests/resource_estimation/templates/test_resource_basisrotation.py deleted file mode 100644 index 54cb70f5902..00000000000 --- a/pennylane/labs/tests/resource_estimation/templates/test_resource_basisrotation.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright 2025 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. -""" -Test the ResourceBasisRotation class -""" -import pytest - -import pennylane as qml -import pennylane.labs.resource_estimation as re - -# pylint: disable=no-self-use - - -class TestBasisRotation: - """Test the ResourceBasisRotation class""" - - op_data = ( - re.ResourceBasisRotation(unitary_matrix=qml.matrix(qml.X(0) @ qml.Y(1)), wires=range(4)), - re.ResourceBasisRotation( - unitary_matrix=qml.matrix(qml.RX(1.23, 0) @ qml.RY(4.56, 1) @ qml.Z(2)), wires=range(8) - ), - re.ResourceBasisRotation( - unitary_matrix=qml.matrix( - qml.Hadamard(0) @ qml.Hadamard(1) @ qml.Hadamard(2) @ qml.Hadamard(3) - ), - wires=range(16), - ), - ) - - resource_data = ( - { - re.ResourcePhaseShift.resource_rep(): 10, - re.ResourceSingleExcitation.resource_rep(): 6, - }, - { - re.ResourcePhaseShift.resource_rep(): 36, - re.ResourceSingleExcitation.resource_rep(): 28, - }, - { - re.ResourcePhaseShift.resource_rep(): 136, - re.ResourceSingleExcitation.resource_rep(): 120, - }, - ) - - resource_params_data = ( - { - "dim_N": 4, - }, - { - "dim_N": 8, - }, - { - "dim_N": 16, - }, - ) - - name_data = ( - "BasisRotation(4)", - "BasisRotation(8)", - "BasisRotation(16)", - ) - - @pytest.mark.parametrize( - "op, params, expected_res", zip(op_data, resource_params_data, resource_data) - ) - def test_resources(self, op, params, expected_res): - """Test the resources method returns the correct dictionary""" - res_from_op = op.resources(**op.resource_params) - res_from_func = re.ResourceBasisRotation.resources(**params) - - assert res_from_op == expected_res - assert res_from_func == expected_res - - @pytest.mark.parametrize("op, expected_params", zip(op_data, resource_params_data)) - def test_resource_params(self, op, expected_params): - """Test that the resource params are correct""" - assert op.resource_params == expected_params - - @pytest.mark.parametrize("expected_params", resource_params_data) - def test_resource_rep(self, expected_params): - """Test the resource_rep returns the correct CompressedResourceOp""" - expected = re.CompressedResourceOp(re.ResourceBasisRotation, expected_params) - assert re.ResourceBasisRotation.resource_rep(**expected_params) == expected - - @pytest.mark.parametrize("params, expected_name", zip(resource_params_data, name_data)) - def test_tracking_name(self, params, expected_name): - """Test that the tracking name is correct.""" - assert re.ResourceBasisRotation.tracking_name(**params) == expected_name diff --git a/pennylane/labs/tests/resource_estimation/templates/test_resource_prepselprep.py b/pennylane/labs/tests/resource_estimation/templates/test_resource_prepselprep.py deleted file mode 100644 index ffd2cc7a907..00000000000 --- a/pennylane/labs/tests/resource_estimation/templates/test_resource_prepselprep.py +++ /dev/null @@ -1,190 +0,0 @@ -# Copyright 2025 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. -""" -Test the ResourcePrepSelPrep class -""" -import copy - -import pytest - -import pennylane.labs.resource_estimation as re -from pennylane.ops import LinearCombination - -# pylint: disable=no-self-use - - -class TestPrepSelPrep: - """Test the ResourcePrepSelPrep class""" - - op_data = ( - re.ResourcePrepSelPrep( - LinearCombination([1.23, -4.5], [re.ResourceX(0), re.ResourceZ(0)]), - control=["c1"], - ), - re.ResourcePrepSelPrep( - LinearCombination( - [1.0, 1.0, 1.0, 1.0], - [ - re.ResourceRX(1.2, 0), - re.ResourceRZ(-3.4, 1), - re.ResourceCNOT([0, 1]), - re.ResourceHadamard(0), - ], - ), - control=["c1", "c2"], - ), - re.ResourcePrepSelPrep( - LinearCombination( - (0.1, -2.3, 4.5, -6, 0.78), - ( - re.ResourceProd(re.ResourceZ(0), re.ResourceZ(1)), - re.ResourceProd(re.ResourceX(0), re.ResourceX(2)), - re.ResourceProd(re.ResourceY(2), re.ResourceY(1)), - re.ResourceAdjoint( - re.ResourceProd(re.ResourceX(0), re.ResourceY(1), re.ResourceZ(2)) - ), - re.ResourceQFT([0, 1, 2]), - ), - ), - control=["c1", "c2", "c3"], - ), - ) - - resource_data = ( - { - re.ResourceStatePrep.resource_rep(num_wires=1): 1, - re.ResourceSelect.resource_rep( - cmpr_ops=( - re.ResourceX.resource_rep(), - re.ResourceZ.resource_rep(), - ), - ): 1, - re.ResourceAdjoint.resource_rep(re.ResourceStatePrep, base_params={"num_wires": 1}): 1, - }, - { - re.ResourceStatePrep.resource_rep(num_wires=2): 1, - re.ResourceSelect.resource_rep( - cmpr_ops=( - re.ResourceRX.resource_rep(), - re.ResourceRZ.resource_rep(), - re.ResourceCNOT.resource_rep(), - re.ResourceHadamard.resource_rep(), - ), - ): 1, - re.ResourceAdjoint.resource_rep(re.ResourceStatePrep, base_params={"num_wires": 2}): 1, - }, - { - re.ResourceStatePrep.resource_rep(num_wires=3): 1, - re.ResourceSelect.resource_rep( - cmpr_ops=( - re.ResourceProd.resource_rep( - (re.ResourceZ.resource_rep(), re.ResourceZ.resource_rep()) - ), - re.ResourceProd.resource_rep( - (re.ResourceX.resource_rep(), re.ResourceX.resource_rep()) - ), - re.ResourceProd.resource_rep( - (re.ResourceY.resource_rep(), re.ResourceY.resource_rep()) - ), - re.ResourceAdjoint.resource_rep( - base_class=re.ResourceProd, - base_params={ - "cmpr_factors": ( - re.ResourceX.resource_rep(), - re.ResourceY.resource_rep(), - re.ResourceZ.resource_rep(), - ) - }, - ), - re.ResourceQFT.resource_rep(num_wires=3), - ), - ): 1, - re.ResourceAdjoint.resource_rep(re.ResourceStatePrep, base_params={"num_wires": 3}): 1, - }, - ) - - resource_params_data = ( - { - "cmpr_ops": ( - re.ResourceX.resource_rep(), - re.ResourceZ.resource_rep(), - ), - }, - { - "cmpr_ops": ( - re.ResourceRX.resource_rep(), - re.ResourceRZ.resource_rep(), - re.ResourceCNOT.resource_rep(), - re.ResourceHadamard.resource_rep(), - ), - }, - { - "cmpr_ops": ( - re.ResourceProd.resource_rep( - (re.ResourceZ.resource_rep(), re.ResourceZ.resource_rep()) - ), - re.ResourceProd.resource_rep( - (re.ResourceX.resource_rep(), re.ResourceX.resource_rep()) - ), - re.ResourceProd.resource_rep( - (re.ResourceY.resource_rep(), re.ResourceY.resource_rep()) - ), - re.ResourceAdjoint.resource_rep( - base_class=re.ResourceProd, - base_params={ - "cmpr_factors": ( - re.ResourceX.resource_rep(), - re.ResourceY.resource_rep(), - re.ResourceZ.resource_rep(), - ) - }, - ), - re.ResourceQFT.resource_rep(num_wires=3), - ), - }, - ) - - @pytest.mark.parametrize( - "op, params, expected_res", zip(op_data, resource_params_data, resource_data) - ) - def test_resources(self, op, params, expected_res): - """Test the resources method returns the correct dictionary""" - res_from_op = op.resources(**op.resource_params) - res_from_func = re.ResourcePrepSelPrep.resources(**params) - - assert res_from_op == expected_res - assert res_from_func == expected_res - - @pytest.mark.parametrize("op, expected_params", zip(op_data, resource_params_data)) - def test_resource_params(self, op, expected_params): - """Test that the resource params are correct""" - assert op.resource_params == expected_params - - @pytest.mark.parametrize("expected_params", resource_params_data) - def test_resource_rep(self, expected_params): - """Test the resource_rep returns the correct CompressedResourceOp""" - expected = re.CompressedResourceOp(re.ResourcePrepSelPrep, expected_params) - assert re.ResourcePrepSelPrep.resource_rep(**expected_params) == expected - - @pytest.mark.parametrize("z", [2, 3, 5]) - @pytest.mark.parametrize("params, base_res", zip(resource_params_data, resource_data)) - def test_pow_resources(self, params, base_res, z): - """Test the pow_resources method returns the correct dictionary""" - pow_select = re.ResourcePow.resource_rep(re.ResourceSelect, base_params=params, z=z) - - expected_res = copy.deepcopy(base_res) - select_res = expected_res.pop(re.ResourceSelect.resource_rep(**params)) - - expected_res[pow_select] = select_res - assert re.ResourcePrepSelPrep.pow_resource_decomp(z=z, **params) == expected_res diff --git a/pennylane/labs/tests/resource_estimation/templates/test_resource_qpe.py b/pennylane/labs/tests/resource_estimation/templates/test_resource_qpe.py deleted file mode 100644 index 52653e12be3..00000000000 --- a/pennylane/labs/tests/resource_estimation/templates/test_resource_qpe.py +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright 2025 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. -""" -Test the ResourceQuantumPhaseEstimation class -""" -import pytest - -import pennylane as qml -import pennylane.labs.resource_estimation as re - -# pylint: disable=no-self-use - - -class TestQuantumPhaseEstimation: - """Test the ResourceQuantumPhaseEstimation class""" - - input_data = ( - ( - re.ResourceHadamard(0), - [1, 2], - ), - ( - re.ResourceRX(1.23, 1), - [2, 3, 4], - ), - ( - re.ResourceCRY(1.23, [0, 1]), - [2, 3, 4, 5], - ), - ( - re.ResourceQFT([0, 1, 2]), - [4, 5], - ), - ) - - resource_data = ( - { - re.ResourceHadamard.resource_rep(): 2, - re.ResourceAdjoint.resource_rep(re.ResourceQFT, {"num_wires": 2}): 1, - re.ResourceControlled.resource_rep(re.ResourceHadamard, {}, 1, 0, 0): 3, - }, - { - re.ResourceRX.resource_rep(): 3, - re.ResourceAdjoint.resource_rep(re.ResourceQFT, {"num_wires": 3}): 1, - re.ResourceControlled.resource_rep(re.ResourceRX, {}, 1, 0, 0): 7, - }, - { - re.ResourceCRY.resource_rep(): 4, - re.ResourceAdjoint.resource_rep(re.ResourceQFT, {"num_wires": 4}): 1, - re.ResourceControlled.resource_rep(re.ResourceCRY, {}, 1, 0, 0): 15, - }, - { - re.ResourceQFT.resource_rep(num_wires=3): 2, - re.ResourceAdjoint.resource_rep(re.ResourceQFT, {"num_wires": 2}): 1, - re.ResourceControlled.resource_rep(re.ResourceQFT, {"num_wires": 3}, 1, 0, 0): 3, - }, - ) - - resource_params_data = ( - { - "base_class": re.ResourceHadamard, - "base_params": {}, - "num_estimation_wires": 2, - }, - { - "base_class": re.ResourceRX, - "base_params": {}, - "num_estimation_wires": 3, - }, - { - "base_class": re.ResourceCRY, - "base_params": {}, - "num_estimation_wires": 4, - }, - { - "base_class": re.ResourceQFT, - "base_params": {"num_wires": 3}, - "num_estimation_wires": 2, - }, - ) - - name_data = ( - "QPE(Hadamard, 2)", - "QPE(RX, 3)", - "QPE(CRY, 4)", - "QPE(QFT(3), 2)", - ) - - @pytest.mark.parametrize( - "num_wires, num_hadamard, num_swap, num_ctrl_phase_shift", - [ - (1, 1, 0, 0), - (2, 2, 1, 1), - (3, 3, 1, 3), - (4, 4, 2, 6), - ], - ) - def test_resources(self, num_wires, num_hadamard, num_swap, num_ctrl_phase_shift): - """Test the resources method returns the correct dictionary""" - hadamard = re.CompressedResourceOp(re.ResourceHadamard, {}) - swap = re.CompressedResourceOp(re.ResourceSWAP, {}) - ctrl_phase_shift = re.CompressedResourceOp(re.ResourceControlledPhaseShift, {}) - - expected = {hadamard: num_hadamard, swap: num_swap, ctrl_phase_shift: num_ctrl_phase_shift} - - assert re.ResourceQFT.resources(num_wires) == expected - - @pytest.mark.parametrize( - "unitary_and_wires, expected_params", zip(input_data, resource_params_data) - ) - def test_resource_params(self, unitary_and_wires, expected_params): - """Test that the resource params are correct""" - unitary, estimation_wires = unitary_and_wires - op = re.ResourceQuantumPhaseEstimation(unitary, estimation_wires=estimation_wires) - assert op.resource_params == expected_params - - def test_resource_params_error(self): - """Test that an error is raised if a resource operator is not provided.""" - with pytest.raises(TypeError, match="Can't obtain QPE resources when"): - op = re.ResourceQuantumPhaseEstimation(qml.Hadamard(0), estimation_wires=[1, 2, 3]) - op.resource_params # pylint: disable=pointless-statement - - @pytest.mark.parametrize("expected_params", resource_params_data) - def test_resource_rep(self, expected_params): - """Test the resource_rep returns the correct CompressedResourceOp""" - expected = re.CompressedResourceOp(re.ResourceQuantumPhaseEstimation, expected_params) - assert re.ResourceQuantumPhaseEstimation.resource_rep(**expected_params) == expected - - @pytest.mark.parametrize("params, expected_name", zip(resource_params_data, name_data)) - def test_tracking_name(self, params, expected_name): - """Test that the tracking name is correct.""" - assert re.ResourceQuantumPhaseEstimation.tracking_name(**params) == expected_name diff --git a/pennylane/labs/tests/resource_estimation/templates/test_resource_qubitization.py b/pennylane/labs/tests/resource_estimation/templates/test_resource_qubitization.py deleted file mode 100644 index 2a9b6fc72a2..00000000000 --- a/pennylane/labs/tests/resource_estimation/templates/test_resource_qubitization.py +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright 2025 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. -""" -Test the ResourceQubitization class -""" -import pytest - -import pennylane.labs.resource_estimation as re -from pennylane.ops import LinearCombination - -# pylint: disable=no-self-use - - -class TestQubitization: - """Test the ResourceQubitization class""" - - op_data = ( - re.ResourceQubitization( - LinearCombination([1.23, -4.5], [re.ResourceX(0), re.ResourceZ(0)]), - control=["c1"], - ), - re.ResourceQubitization( - LinearCombination( - [1.0, 1.0, 1.0, 1.0], - [ - re.ResourceRX(1.2, 0), - re.ResourceRZ(-3.4, 1), - re.ResourceCNOT([0, 1]), - re.ResourceHadamard(0), - ], - ), - control=["c1", "c2"], - ), - re.ResourceQubitization( - LinearCombination( - (0.1, -2.3, 4.5, -6, 0.78), - ( - re.ResourceProd(re.ResourceZ(0), re.ResourceZ(1)), - re.ResourceProd(re.ResourceX(0), re.ResourceX(2)), - re.ResourceProd(re.ResourceY(2), re.ResourceY(1)), - re.ResourceAdjoint( - re.ResourceProd(re.ResourceX(0), re.ResourceY(1), re.ResourceZ(2)) - ), - re.ResourceQFT([0, 1, 2]), - ), - ), - control=["c1", "c2", "c3"], - ), - ) - - resource_data = ( - { - re.ResourceReflection.resource_rep(re.ResourceIdentity, {}, 1): 1, - re.ResourcePrepSelPrep.resource_rep( - cmpr_ops=( - re.ResourceX.resource_rep(), - re.ResourceZ.resource_rep(), - ), - ): 1, - }, - { - re.ResourceReflection.resource_rep(re.ResourceIdentity, {}, 2): 1, - re.ResourcePrepSelPrep.resource_rep( - cmpr_ops=( - re.ResourceRX.resource_rep(), - re.ResourceRZ.resource_rep(), - re.ResourceCNOT.resource_rep(), - re.ResourceHadamard.resource_rep(), - ), - ): 1, - }, - { - re.ResourceReflection.resource_rep(re.ResourceIdentity, {}, 3): 1, - re.ResourcePrepSelPrep.resource_rep( - cmpr_ops=( - re.ResourceProd.resource_rep( - (re.ResourceZ.resource_rep(), re.ResourceZ.resource_rep()) - ), - re.ResourceProd.resource_rep( - (re.ResourceX.resource_rep(), re.ResourceX.resource_rep()) - ), - re.ResourceProd.resource_rep( - (re.ResourceY.resource_rep(), re.ResourceY.resource_rep()) - ), - re.ResourceAdjoint.resource_rep( - base_class=re.ResourceProd, - base_params={ - "cmpr_factors": ( - re.ResourceX.resource_rep(), - re.ResourceY.resource_rep(), - re.ResourceZ.resource_rep(), - ) - }, - ), - re.ResourceQFT.resource_rep(num_wires=3), - ), - ): 1, - }, - ) - - resource_params_data = ( - { - "cmpr_ops": ( - re.ResourceX.resource_rep(), - re.ResourceZ.resource_rep(), - ), - "num_ctrl_wires": 1, - }, - { - "cmpr_ops": ( - re.ResourceRX.resource_rep(), - re.ResourceRZ.resource_rep(), - re.ResourceCNOT.resource_rep(), - re.ResourceHadamard.resource_rep(), - ), - "num_ctrl_wires": 2, - }, - { - "cmpr_ops": ( - re.ResourceProd.resource_rep( - (re.ResourceZ.resource_rep(), re.ResourceZ.resource_rep()) - ), - re.ResourceProd.resource_rep( - (re.ResourceX.resource_rep(), re.ResourceX.resource_rep()) - ), - re.ResourceProd.resource_rep( - (re.ResourceY.resource_rep(), re.ResourceY.resource_rep()) - ), - re.ResourceAdjoint.resource_rep( - base_class=re.ResourceProd, - base_params={ - "cmpr_factors": ( - re.ResourceX.resource_rep(), - re.ResourceY.resource_rep(), - re.ResourceZ.resource_rep(), - ) - }, - ), - re.ResourceQFT.resource_rep(num_wires=3), - ), - "num_ctrl_wires": 3, - }, - ) - - @pytest.mark.parametrize( - "op, params, expected_res", zip(op_data, resource_params_data, resource_data) - ) - def test_resources(self, op, params, expected_res): - """Test the resources method returns the correct dictionary""" - res_from_op = op.resources(**op.resource_params) - res_from_func = re.ResourceQubitization.resources(**params) - - assert res_from_op == expected_res - assert res_from_func == expected_res - - @pytest.mark.parametrize("op, expected_params", zip(op_data, resource_params_data)) - def test_resource_params(self, op, expected_params): - """Test that the resource params are correct""" - assert op.resource_params == expected_params - - @pytest.mark.parametrize("expected_params", resource_params_data) - def test_resource_rep(self, expected_params): - """Test the resource_rep returns the correct CompressedResourceOp""" - expected = re.CompressedResourceOp(re.ResourceQubitization, expected_params) - assert re.ResourceQubitization.resource_rep(**expected_params) == expected diff --git a/pennylane/labs/tests/resource_estimation/templates/test_resource_reflection.py b/pennylane/labs/tests/resource_estimation/templates/test_resource_reflection.py deleted file mode 100644 index 3b1cb708ad9..00000000000 --- a/pennylane/labs/tests/resource_estimation/templates/test_resource_reflection.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2025 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. -""" -Test the ResourceReflection class -""" -import pytest - -import pennylane.labs.resource_estimation as re - -# pylint: disable=no-self-use - - -class TestReflection: - """Test the ResourceReflection class""" - - op_data = ( - re.ResourceReflection(re.ResourceHadamard(0)), - re.ResourceReflection(re.ResourceProd(re.ResourceX(0), re.ResourceY(1)), alpha=1.23), - re.ResourceReflection(re.ResourceQFT(range(4)), reflection_wires=range(3)), - ) - - resource_data = ( - { - re.ResourceX.resource_rep(): 2, - re.ResourceGlobalPhase.resource_rep(): 1, - re.ResourceHadamard.resource_rep(): 1, - re.ResourceAdjoint.resource_rep(base_class=re.ResourceHadamard, base_params={}): 1, - re.ResourcePhaseShift.resource_rep(): 1, - }, - { - re.ResourceX.resource_rep(): 2, - re.ResourceGlobalPhase.resource_rep(): 1, - re.ResourceProd.resource_rep( - cmpr_factors=(re.ResourceX.resource_rep(), re.ResourceY.resource_rep()) - ): 1, - re.ResourceAdjoint.resource_rep( - base_class=re.ResourceProd, - base_params={ - "cmpr_factors": (re.ResourceX.resource_rep(), re.ResourceY.resource_rep()) - }, - ): 1, - re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 1, 1, 0): 1, - }, - { - re.ResourceX.resource_rep(): 2, - re.ResourceGlobalPhase.resource_rep(): 1, - re.ResourceQFT.resource_rep(num_wires=4): 1, - re.ResourceAdjoint.resource_rep( - base_class=re.ResourceQFT, base_params={"num_wires": 4} - ): 1, - re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 2, 2, 0): 1, - }, - ) - - resource_params_data = ( - { - "base_class": re.ResourceHadamard, - "base_params": {}, - "num_ref_wires": 1, - }, - { - "base_class": re.ResourceProd, - "base_params": { - "cmpr_factors": (re.ResourceX.resource_rep(), re.ResourceY.resource_rep()) - }, - "num_ref_wires": 2, - }, - { - "base_class": re.ResourceQFT, - "base_params": {"num_wires": 4}, - "num_ref_wires": 3, - }, - ) - - @pytest.mark.parametrize( - "op, params, expected_res", zip(op_data, resource_params_data, resource_data) - ) - def test_resources(self, op, params, expected_res): - """Test the resources method returns the correct dictionary""" - res_from_op = op.resources(**op.resource_params) - res_from_func = re.ResourceReflection.resources(**params) - - assert res_from_op == expected_res - assert res_from_func == expected_res - - @pytest.mark.parametrize("op, expected_params", zip(op_data, resource_params_data)) - def test_resource_params(self, op, expected_params): - """Test that the resource params are correct""" - assert op.resource_params == expected_params - - @pytest.mark.parametrize("expected_params", resource_params_data) - def test_resource_rep(self, expected_params): - """Test the resource_rep returns the correct CompressedResourceOp""" - expected = re.CompressedResourceOp(re.ResourceReflection, expected_params) - assert re.ResourceReflection.resource_rep(**expected_params) == expected diff --git a/pennylane/labs/tests/resource_estimation/templates/test_resource_select.py b/pennylane/labs/tests/resource_estimation/templates/test_resource_select.py deleted file mode 100644 index 0d88801d756..00000000000 --- a/pennylane/labs/tests/resource_estimation/templates/test_resource_select.py +++ /dev/null @@ -1,190 +0,0 @@ -# Copyright 2025 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. -""" -Test the ResourceSelect class -""" -import pytest - -import pennylane.labs.resource_estimation as re - -# pylint: disable=no-self-use - - -class TestSelect: - """Test the ResourceSelect class""" - - op_data = ( - re.ResourceSelect( - [re.ResourceX(0), re.ResourceY(1)], - control=["c1"], - ), - re.ResourceSelect( - [re.ResourceCNOT((0, 1)), re.ResourceHadamard(1), re.ResourceQFT(range(3))], - control=["c1", "c2"], - ), - re.ResourceSelect( - [ - re.ResourceCNOT((0, 1)), - re.ResourceHadamard(1), - re.ResourceT(2), - re.ResourceS(3), - re.ResourceZ(0), - re.ResourceY(1), - re.ResourceX(3), - ], - control=["c1", "c2", "c3"], - ), - ) - - resource_params_data = ( - { - "cmpr_ops": (re.ResourceX.resource_rep(), re.ResourceY.resource_rep()), - }, - { - "cmpr_ops": ( - re.ResourceCNOT.resource_rep(), - re.ResourceHadamard.resource_rep(), - re.ResourceQFT.resource_rep(num_wires=3), - ), - }, - { - "cmpr_ops": ( - re.ResourceCNOT.resource_rep(), - re.ResourceHadamard.resource_rep(), - re.ResourceT.resource_rep(), - re.ResourceS.resource_rep(), - re.ResourceZ.resource_rep(), - re.ResourceY.resource_rep(), - re.ResourceX.resource_rep(), - ), - }, - ) - - resource_data = ( - { - re.ResourceX.resource_rep(): 2, - re.ResourceControlled.resource_rep( - re.ResourceX, - {}, - 1, - 0, - 0, - ): 1, - re.ResourceControlled.resource_rep( - re.ResourceY, - {}, - 1, - 0, - 0, - ): 1, - }, - { - re.ResourceX.resource_rep(): 4, - re.ResourceControlled.resource_rep( - re.ResourceCNOT, - {}, - 2, - 0, - 0, - ): 1, - re.ResourceControlled.resource_rep( - re.ResourceHadamard, - {}, - 2, - 0, - 0, - ): 1, - re.ResourceControlled.resource_rep( - re.ResourceQFT, - {"num_wires": 3}, - 2, - 0, - 0, - ): 1, - }, - { - re.ResourceX.resource_rep(): 8, - re.ResourceControlled.resource_rep( - re.ResourceCNOT, - {}, - 3, - 0, - 0, - ): 1, - re.ResourceControlled.resource_rep( - re.ResourceHadamard, - {}, - 3, - 0, - 0, - ): 1, - re.ResourceControlled.resource_rep( - re.ResourceT, - {}, - 3, - 0, - 0, - ): 1, - re.ResourceControlled.resource_rep( - re.ResourceS, - {}, - 3, - 0, - 0, - ): 1, - re.ResourceControlled.resource_rep( - re.ResourceZ, - {}, - 3, - 0, - 0, - ): 1, - re.ResourceControlled.resource_rep( - re.ResourceY, - {}, - 3, - 0, - 0, - ): 1, - re.ResourceControlled.resource_rep( - re.ResourceX, - {}, - 3, - 0, - 0, - ): 1, - }, - ) - - @pytest.mark.parametrize( - "op, params, expected_res", zip(op_data, resource_params_data, resource_data) - ) - def test_resources(self, op, params, expected_res): - """Test the resources method returns the correct dictionary""" - res_from_op = op.resources(**op.resource_params) - res_from_func = re.ResourceSelect.resources(**params) - - assert res_from_op == expected_res - assert res_from_func == expected_res - - @pytest.mark.parametrize("op, expected_params", zip(op_data, resource_params_data)) - def test_resource_params(self, op, expected_params): - """Test that the resource params are correct""" - assert op.resource_params == expected_params - - @pytest.mark.parametrize("expected_params", resource_params_data) - def test_resource_rep(self, expected_params): - """Test the resource_rep returns the correct CompressedResourceOp""" - expected = re.CompressedResourceOp(re.ResourceSelect, expected_params) - assert re.ResourceSelect.resource_rep(**expected_params) == expected diff --git a/pennylane/labs/tests/resource_estimation/templates/test_resource_stateprep.py b/pennylane/labs/tests/resource_estimation/templates/test_resource_stateprep.py deleted file mode 100644 index f2feb2d2e49..00000000000 --- a/pennylane/labs/tests/resource_estimation/templates/test_resource_stateprep.py +++ /dev/null @@ -1,396 +0,0 @@ -# Copyright 2025 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. -""" -Test the ResourceStatePrep class -""" -import math - -import pytest - -import pennylane.labs.resource_estimation as re -from pennylane import numpy as qnp - -# pylint: disable=no-self-use - - -class TestStatePrep: - """Test the ResourceStatePrep class""" - - op_data = ( - re.ResourceStatePrep([1, 0], wires=[0]), - re.ResourceStatePrep(qnp.random.rand(2**3), wires=range(3), normalize=True), - re.ResourceStatePrep(qnp.random.rand(10), wires=range(4), normalize=True, pad_with=0), - re.ResourceStatePrep(qnp.random.rand(2**5), wires=range(5), normalize=True), - ) - - resource_data = ( - { - re.ResourceMottonenStatePreparation.resource_rep(1): 1, - }, - { - re.ResourceMottonenStatePreparation.resource_rep(3): 1, - }, - { - re.ResourceMottonenStatePreparation.resource_rep(4): 1, - }, - { - re.ResourceMottonenStatePreparation.resource_rep(5): 1, - }, - ) - - resource_params_data = ( - { - "num_wires": 1, - }, - { - "num_wires": 3, - }, - { - "num_wires": 4, - }, - { - "num_wires": 5, - }, - ) - - name_data = ( - "StatePrep(1)", - "StatePrep(3)", - "StatePrep(4)", - "StatePrep(5)", - ) - - @pytest.mark.parametrize( - "op, params, expected_res", zip(op_data, resource_params_data, resource_data) - ) - def test_resources(self, op, params, expected_res): - """Test the resources method returns the correct dictionary""" - res_from_op = op.resources(**op.resource_params) - res_from_func = re.ResourceStatePrep.resources(**params) - - assert res_from_op == expected_res - assert res_from_func == expected_res - - @pytest.mark.parametrize("op, expected_params", zip(op_data, resource_params_data)) - def test_resource_params(self, op, expected_params): - """Test that the resource params are correct""" - assert op.resource_params == expected_params - - @pytest.mark.parametrize("expected_params", resource_params_data) - def test_resource_rep(self, expected_params): - """Test the resource_rep returns the correct CompressedResourceOp""" - expected = re.CompressedResourceOp(re.ResourceStatePrep, expected_params) - assert re.ResourceStatePrep.resource_rep(**expected_params) == expected - - @pytest.mark.parametrize("params, expected_name", zip(resource_params_data, name_data)) - def test_tracking_name(self, params, expected_name): - """Test that the tracking name is correct.""" - assert re.ResourceStatePrep.tracking_name(**params) == expected_name - - -class TestResourceBasisState: - """Test the ResourceBasisState class""" - - @pytest.mark.parametrize( - "num_bit_flips, num_x", - [(4, 4), (5, 5), (6, 6)], - ) - def test_resources(self, num_bit_flips, num_x): - """Test that the resources are correct""" - expected = {} - x = re.CompressedResourceOp(re.ResourceX, {}) - expected[x] = num_x - - assert re.ResourceBasisState.resources(num_bit_flips) == expected - - @pytest.mark.parametrize( - "state, wires", - [ - ( - [1, 1], - range(2), - ), - ], - ) - def test_resource_params(self, state, wires): - """Test that the resource params are correct""" - op = re.ResourceBasisState(state, wires=wires) - - assert op.resource_params == {"num_bit_flips": 2} - - @pytest.mark.parametrize( - "num_bit_flips", - [(4, 4), (5, 5), (6, 6)], - ) - def test_resource_rep(self, num_bit_flips): - """Test the resource_rep returns the correct CompressedResourceOp""" - - expected = re.CompressedResourceOp( - re.ResourceBasisState, - {"num_bit_flips": num_bit_flips}, - ) - assert expected == re.ResourceBasisState.resource_rep(num_bit_flips) - - @pytest.mark.parametrize( - "num_bit_flips, num_x", - [(4, 4), (5, 5), (6, 6)], - ) - def test_resources_from_rep(self, num_bit_flips, num_x): - """Test that computing the resources from a compressed representation works""" - rep = re.ResourceBasisState.resource_rep(num_bit_flips) - actual = rep.op_type.resources(**rep.params) - expected = {} - x = re.CompressedResourceOp(re.ResourceX, {}) - expected[x] = num_x - - assert actual == expected - - @pytest.mark.parametrize( - "num_bit_flips", - [(4), (5), (6)], - ) - def test_tracking_name(self, num_bit_flips): - """Test that the tracking name is correct.""" - assert re.ResourceBasisState.tracking_name(num_bit_flips) == f"BasisState({num_bit_flips})" - - -class TestResourceSuperposition: - """Test the ResourceSuperposition class""" - - @pytest.mark.parametrize( - "num_stateprep_wires, num_basis_states, size_basis_state", - [(4, 2, 2), (4, 5, 2), (4, 5, 0)], - ) - def test_resources(self, num_stateprep_wires, num_basis_states, size_basis_state): - """Test that the resources are correct""" - expected = {} - msp = re.CompressedResourceOp( - re.ResourceMottonenStatePreparation, {"num_wires": num_stateprep_wires} - ) - expected[msp] = 1 - - cnot = re.CompressedResourceOp(re.ResourceCNOT, {}) - num_zero_ctrls = size_basis_state // 2 - multi_x = re.CompressedResourceOp( - re.ResourceMultiControlledX, - { - "num_ctrl_wires": size_basis_state, - "num_ctrl_values": num_zero_ctrls, - "num_work_wires": 0, - }, - ) - - basis_size = 2**size_basis_state - prob_matching_basis_states = num_basis_states / basis_size - num_permutes = round(num_basis_states * (1 - prob_matching_basis_states)) - - if num_permutes: - expected[cnot] = num_permutes * ( - size_basis_state // 2 - ) # average number of bits to flip - expected[multi_x] = 2 * num_permutes # for compute and uncompute - - assert ( - re.ResourceSuperposition.resources( - num_stateprep_wires, num_basis_states, size_basis_state - ) - == expected - ) - - @pytest.mark.parametrize( - "coeffs, bases, wires, work_wire", - [ - ( - qnp.sqrt(qnp.array([1 / 3, 1 / 3, 1 / 3])), - qnp.array([[1, 1, 1], [0, 1, 0], [0, 0, 0]]), - [0, 1, 2], - [3], - ), - ], - ) - def test_resource_params(self, coeffs, bases, wires, work_wire): - """Test that the resource params are correct""" - op = re.ResourceSuperposition(coeffs, bases, wires, work_wire) - - num_basis_states = len(bases) - size_basis_state = len(bases[0]) # assuming they are all the same size - num_stateprep_wires = math.ceil(math.log2(len(coeffs))) - - assert op.resource_params == { - "num_stateprep_wires": num_stateprep_wires, - "num_basis_states": num_basis_states, - "size_basis_state": size_basis_state, - } - - @pytest.mark.parametrize( - "num_stateprep_wires, num_basis_states, size_basis_state", - [(4, 2, 2), (4, 5, 2), (4, 5, 0)], - ) - def test_resource_rep(self, num_stateprep_wires, num_basis_states, size_basis_state): - """Test the resource_rep returns the correct CompressedResourceOp""" - - expected = re.CompressedResourceOp( - re.ResourceSuperposition, - { - "num_stateprep_wires": num_stateprep_wires, - "num_basis_states": num_basis_states, - "size_basis_state": size_basis_state, - }, - ) - assert expected == re.ResourceSuperposition.resource_rep( - num_stateprep_wires, num_basis_states, size_basis_state - ) - - @pytest.mark.parametrize( - "num_stateprep_wires, num_basis_states, size_basis_state", - [(4, 2, 2), (4, 5, 2), (4, 5, 0)], - ) - def test_resources_from_rep(self, num_stateprep_wires, num_basis_states, size_basis_state): - """Test that computing the resources from a compressed representation works""" - expected = {} - rep = re.ResourceSuperposition.resource_rep( - num_stateprep_wires, num_basis_states, size_basis_state - ) - actual = rep.op_type.resources(**rep.params) - - expected = {} - msp = re.CompressedResourceOp( - re.ResourceMottonenStatePreparation, {"num_wires": num_stateprep_wires} - ) - expected[msp] = 1 - - cnot = re.CompressedResourceOp(re.ResourceCNOT, {}) - num_zero_ctrls = size_basis_state // 2 - multi_x = re.CompressedResourceOp( - re.ResourceMultiControlledX, - { - "num_ctrl_wires": size_basis_state, - "num_ctrl_values": num_zero_ctrls, - "num_work_wires": 0, - }, - ) - - basis_size = 2**size_basis_state - prob_matching_basis_states = num_basis_states / basis_size - num_permutes = round(num_basis_states * (1 - prob_matching_basis_states)) - - if num_permutes: - expected[cnot] = num_permutes * ( - size_basis_state // 2 - ) # average number of bits to flip - expected[multi_x] = 2 * num_permutes # for compute and uncompute - - assert actual == expected - - def test_tracking_name(self): - """Test that the tracking name is correct.""" - assert re.ResourceSuperposition.tracking_name() == "Superposition" - - -class TestResourceMottonenStatePreparation: - """Test the ResourceMottonenStatePreparation class""" - - @pytest.mark.parametrize( - "num_wires", - [(4), (5), (6)], - ) - def test_resources(self, num_wires): - """Test that the resources are correct""" - expected = {} - rz = re.CompressedResourceOp(re.ResourceRZ, {}) - cnot = re.CompressedResourceOp(re.ResourceCNOT, {}) - - r_count = 2 ** (num_wires + 2) - 5 - cnot_count = 2 ** (num_wires + 2) - 4 * num_wires - 4 - - if r_count: - expected[rz] = r_count - - if cnot_count: - expected[cnot] = cnot_count - assert re.ResourceMottonenStatePreparation.resources(num_wires) == expected - - @pytest.mark.parametrize( - "state_vector, wires", - [ - ( - qnp.array( - [ - 0.070014 + 0.0j, - 0.0 + 0.14002801j, - 0.21004201 + 0.0j, - 0.0 + 0.28005602j, - 0.35007002 + 0.0j, - 0.0 + 0.42008403j, - 0.49009803 + 0.0j, - 0.0 + 0.56011203j, - ] - ), - range(3), - ), - ], - ) - def test_resource_params(self, state_vector, wires): - """Test that the resource params are correct""" - op = re.ResourceMottonenStatePreparation(state_vector=state_vector, wires=wires) - - assert op.resource_params == {"num_wires": len(wires)} - - @pytest.mark.parametrize( - "num_wires", - [(4), (5), (6)], - ) - def test_resource_rep(self, num_wires): - """Test the resource_rep returns the correct CompressedResourceOp""" - - expected = re.CompressedResourceOp( - re.ResourceMottonenStatePreparation, - {"num_wires": num_wires}, - ) - assert expected == re.ResourceMottonenStatePreparation.resource_rep(num_wires) - - @pytest.mark.parametrize( - "num_wires", - [(4), (5), (6)], - ) - def test_resources_from_rep(self, num_wires): - """Test that computing the resources from a compressed representation works""" - rep = re.ResourceMottonenStatePreparation.resource_rep(num_wires) - actual = rep.op_type.resources(**rep.params) - - expected = {} - rz = re.CompressedResourceOp(re.ResourceRZ, {}) - cnot = re.CompressedResourceOp(re.ResourceCNOT, {}) - - r_count = 2 ** (num_wires + 2) - 5 - cnot_count = 2 ** (num_wires + 2) - 4 * num_wires - 4 - - if r_count: - expected[rz] = r_count - - if cnot_count: - expected[cnot] = cnot_count - - assert actual == expected - - @pytest.mark.parametrize( - "num_wires", - [(4), (5), (6)], - ) - def test_tracking_name(self, num_wires): - """Test that the tracking name is correct.""" - assert ( - re.ResourceMottonenStatePreparation.tracking_name(num_wires) - == f"MottonenStatePrep({num_wires})" - ) diff --git a/pennylane/labs/tests/resource_estimation/templates/test_resource_trotter.py b/pennylane/labs/tests/resource_estimation/templates/test_resource_trotter.py deleted file mode 100644 index f99dfd001ce..00000000000 --- a/pennylane/labs/tests/resource_estimation/templates/test_resource_trotter.py +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright 2025 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. -""" -Test the Resource classes associated with TrotterProduct and TrotterizedQfunc -""" -import pytest - -import pennylane as qml -import pennylane.labs.resource_estimation as re -from pennylane.operation import Operation -from pennylane.ops.op_math import LinearCombination, SProd - -# pylint: disable=no-self-use,arguments-differ - - -def qfunc_1(time, wires): - """A quantum function which queues operations to be trotterized.""" - re.ResourceRX(1.23 * time, wires[0]) - re.ResourceRY(-4.5 * time, wires[0]) - - -def qfunc_2(time, arg1, wires): - """A quantum function which queues operations to be trotterized.""" - for w in wires: - re.ResourceHadamard(w) - - re.ResourceQFT(wires) - re.ResourceRX(arg1 * time, wires[0]) - - -def qfunc_3(time, arg1, arg2, kwarg1=False, wires=None): - """A quantum function which queues operations to be trotterized.""" - re.ResourceControlled( - re.ResourceRot(arg1 * time, arg2 * time, time * (arg1 + arg2), wires=wires[1]), - control_wires=wires[0], - ) - if kwarg1: - for w in wires: - re.ResourceHadamard(w) - - -trotterized_qfunc_op_data = ( - re.ResourceTrotterizedQfunc(1, qfunc=qfunc_1, n=5, order=2, reverse=False, wires=[0]), - re.ResourceTrotterizedQfunc( - 1, *(1.23,), qfunc=qfunc_2, n=10, order=2, reverse=False, wires=[0, 1, 2] - ), - re.ResourceTrotterizedQfunc( - 1, - *(1.23, -4.56), - qfunc=qfunc_3, - n=10, - order=4, - reverse=False, - wires=[0, 1], - **{"kwarg1": True}, - ), -) - - -class DummyOp(re.ResourceOperator, Operation): - """A Dummy ResourceOperator child class which implements the - :code:`exp_resource_decomp` method.""" - - def __init__(self, a, b, wires=(0,)): - self.a = a - self.b = b - super().__init__(wires=wires) - - @staticmethod - def _resource_decomp(a, b, **kwargs): - h = re.ResourceHadamard.resource_rep() - cnot = re.ResourceCNOT.resource_rep() - - return {h: a, cnot: b} - - @classmethod - def exp_resource_decomp(cls, coeff, num_steps, a, b): # pylint: disable=unused-argument - return cls.resources(a + 1, b + 1) - - @property - def resource_params(self) -> dict: - return {"a": self.a, "b": self.b} - - @classmethod - def resource_rep(cls, a, b): - return re.CompressedResourceOp(cls, {"a": a, "b": b}) - - -class TestResourceTrotterProduct: - """Test the ResourceTrotterProduct class""" - - op_data = ( - re.ResourceTrotterProduct( - qml.sum( - DummyOp(a=1, b=2), - re.ResourceProd(re.ResourceZ(0), re.ResourceZ(1)), - LinearCombination( - [1.2, 3.4, 5.6], [re.ResourceX(0), re.ResourceY(0), re.ResourceZ(1)] - ), - ), - time=0.1, - check_hermitian=False, - ), - re.ResourceTrotterProduct( - LinearCombination( - coeffs=[0.1, 0.2], - observables=[ - re.ResourceX(0), - re.ResourceProd(re.ResourceZ(0), re.ResourceZ(1)), - ], - ), - time=0.1, - n=5, - order=2, - ), - re.ResourceTrotterProduct( - LinearCombination( - coeffs=[1.2, -3.4], - observables=[re.ResourceZ(0), re.ResourceX(1)], - ), - time=0.1, - n=10, - order=4, - ), - ) - - resource_params_data = ( - { - "n": 1, - "order": 1, - "first_order_expansion": [ - re.ResourceExp.resource_rep(DummyOp, {"a": 1, "b": 2}, None, 1j, 1), - re.ResourceExp.resource_rep( - re.ResourceProd, - {"cmpr_factors": (re.ResourceZ.resource_rep(), re.ResourceZ.resource_rep())}, - (qml.Z(0) @ qml.Z(1)).pauli_rep, - 1j, - 1, - ), - re.ResourceExp.resource_rep( - LinearCombination, - {}, - (1.2 * qml.X(0) + 3.4 * qml.Y(0) + 5.6 * qml.Z(1)).pauli_rep, - 1j, - 1, - ), - ], - }, - { - "n": 5, - "order": 2, - "first_order_expansion": [ - re.ResourceExp.resource_rep(SProd, {}, (0.1 * qml.X(0)).pauli_rep, 1j, 1), - re.ResourceExp.resource_rep( - SProd, {}, (0.2 * qml.Z(0) @ qml.Z(1)).pauli_rep, 1j, 1 - ), - ], - }, - { - "n": 10, - "order": 4, - "first_order_expansion": [ - re.ResourceExp.resource_rep(SProd, {}, (1.2 * qml.Z(0)).pauli_rep, 1j, 1), - re.ResourceExp.resource_rep(SProd, {}, (-3.4 * qml.X(1)).pauli_rep, 1j, 1), - ], - }, - ) - - resource_data = ( - { - re.ResourceExp.resource_rep(DummyOp, {"a": 1, "b": 2}, None, 1j, 1): 1, - re.ResourceExp.resource_rep( - re.ResourceProd, - {"cmpr_factors": (re.ResourceZ.resource_rep(), re.ResourceZ.resource_rep())}, - (qml.Z(0) @ qml.Z(1)).pauli_rep, - 1j, - 1, - ): 1, - re.ResourceExp.resource_rep( - LinearCombination, - {}, - (1.2 * qml.X(0) + 3.4 * qml.Y(0) + 5.6 * qml.Z(1)).pauli_rep, - 1j, - 1, - ): 1, - }, - { - re.ResourceExp.resource_rep(SProd, {}, (0.1 * qml.X(0)).pauli_rep, 1j, 1): 6, - re.ResourceExp.resource_rep(SProd, {}, (0.2 * qml.Z(0) @ qml.Z(1)).pauli_rep, 1j, 1): 5, - }, - { - re.ResourceExp.resource_rep(SProd, {}, (1.2 * qml.Z(0)).pauli_rep, 1j, 1): 51, - re.ResourceExp.resource_rep(SProd, {}, (-3.4 * qml.X(1)).pauli_rep, 1j, 1): 50, - }, - ) - - @pytest.mark.parametrize("op, expected_res", zip(op_data, resource_data)) - def test_resources(self, op, expected_res): - """Test the resources method returns the correct dictionary""" - op_rep = op.resource_rep_from_op() - computed_res = op_rep.op_type.resources(**op_rep.params) - assert computed_res == expected_res - - @pytest.mark.parametrize("op, expected_params", zip(op_data, resource_params_data)) - def test_resource_params(self, op, expected_params): - """Test that the resource params are correct""" - assert op.resource_params == expected_params - - @pytest.mark.parametrize("params", resource_params_data) - def test_resource_rep(self, params): - """Test the resource_rep returns the correct CompressedResourceOp""" - expected = re.CompressedResourceOp(re.ResourceTrotterProduct, params) - assert re.ResourceTrotterProduct.resource_rep(**params) == expected - - -class TestResourceTrotterizedQfunc: - """Test the ResourceTrotterizedQfunc class""" - - resource_params_data = ( - { - "n": 5, - "order": 2, - "qfunc_compressed_reps": ( - re.ResourceRX.resource_rep(), - re.ResourceRY.resource_rep(), - ), - }, - { - "n": 10, - "order": 2, - "qfunc_compressed_reps": ( - re.ResourceHadamard.resource_rep(), - re.ResourceHadamard.resource_rep(), - re.ResourceHadamard.resource_rep(), - re.ResourceQFT.resource_rep(num_wires=3), - re.ResourceRX.resource_rep(), - ), - }, - { - "n": 10, - "order": 4, - "qfunc_compressed_reps": ( - re.ResourceControlled.resource_rep(re.ResourceRot, {}, 1, 0, 0), - re.ResourceHadamard.resource_rep(), - re.ResourceHadamard.resource_rep(), - ), - }, - ) - - resource_data = ( - { - re.ResourceRX.resource_rep(): 10, - re.ResourceRY.resource_rep(): 10, - }, - { - re.ResourceHadamard.resource_rep(): 60, - re.ResourceQFT.resource_rep(num_wires=3): 20, - re.ResourceRX.resource_rep(): 20, - }, - { - re.ResourceControlled.resource_rep(re.ResourceRot, {}, 1, 0, 0): 100, - re.ResourceHadamard.resource_rep(): 200, - }, - ) - - @pytest.mark.parametrize("op, expected_res", zip(trotterized_qfunc_op_data, resource_data)) - def test_resources(self, op, expected_res): - """Test the resources method returns the correct dictionary""" - op_rep = op.resource_rep_from_op() - computed_res = op_rep.op_type.resources(**op_rep.params) - assert computed_res == expected_res - - @pytest.mark.parametrize( - "op, expected_params", zip(trotterized_qfunc_op_data, resource_params_data) - ) - def test_resource_params(self, op, expected_params): - """Test that the resource params are correct""" - assert op.resource_params == expected_params - - @pytest.mark.parametrize("params", resource_params_data) - def test_resource_rep(self, params): - """Test the resource_rep returns the correct CompressedResourceOp""" - expected = re.CompressedResourceOp(re.ResourceTrotterizedQfunc, params) - assert re.ResourceTrotterizedQfunc.resource_rep(**params) == expected - - -@pytest.mark.parametrize( - "qfunc, args_n_kwargs, hyperparams, expected_op", - zip( - (qfunc_1, qfunc_2, qfunc_3), - ( - ((1,), {"wires": [0]}), - ((1, 1.23), {"wires": [0, 1, 2]}), - ((1, 1.23, -4.56), {"kwarg1": True, "wires": [0, 1]}), - ), - ( - {"n": 5, "order": 2}, - {"n": 10, "order": 2}, - {"n": 10, "order": 4}, - ), - trotterized_qfunc_op_data, - ), -) -def test_resource_trotterize(qfunc, args_n_kwargs, hyperparams, expected_op): - """Test that the resource_trotterize wrapper function generates an instance of - ResourceTrotterizedQfunc.""" - args, kwargs = args_n_kwargs - assert qml.equal( - re.resource_trotterize(qfunc=qfunc, **hyperparams)(*args, **kwargs), expected_op - ) diff --git a/pennylane/labs/tests/resource_estimation/templates/test_subroutines.py b/pennylane/labs/tests/resource_estimation/templates/test_subroutines.py deleted file mode 100644 index 846981e9186..00000000000 --- a/pennylane/labs/tests/resource_estimation/templates/test_subroutines.py +++ /dev/null @@ -1,1041 +0,0 @@ -# Copyright 2025 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. -""" -Test the ResourceQFT class -""" -import math - -import numpy as np -import pytest - -import pennylane as qml -import pennylane.labs.resource_estimation as re - -# pylint: disable=no-self-use, too-many-arguments, protected-access - - -class TestQFT: - """Test the ResourceQFT class""" - - @pytest.mark.parametrize( - "num_wires, num_hadamard, num_swap, num_ctrl_phase_shift", - [ - (1, 1, 0, 0), - (2, 2, 1, 1), - (3, 3, 1, 3), - (4, 4, 2, 6), - ], - ) - def test_resources(self, num_wires, num_hadamard, num_swap, num_ctrl_phase_shift): - """Test the resources method returns the correct dictionary""" - hadamard = re.CompressedResourceOp(re.ResourceHadamard, {}) - swap = re.CompressedResourceOp(re.ResourceSWAP, {}) - ctrl_phase_shift = re.CompressedResourceOp(re.ResourceControlledPhaseShift, {}) - - expected = {hadamard: num_hadamard, swap: num_swap, ctrl_phase_shift: num_ctrl_phase_shift} - - assert re.ResourceQFT.resources(num_wires) == expected - - @pytest.mark.parametrize("wires", [range(1), range(2), range(3), range(4)]) - def test_resource_params(self, wires): - """Test that the resource params are correct""" - op = re.ResourceQFT(wires) - assert op.resource_params == {"num_wires": len(wires)} - - @pytest.mark.parametrize("num_wires", [1, 2, 3, 4]) - def test_resource_rep(self, num_wires): - """Test the resource_rep returns the correct CompressedResourceOp""" - - expected = re.CompressedResourceOp(re.ResourceQFT, {"num_wires": num_wires}) - assert re.ResourceQFT.resource_rep(num_wires) == expected - - @pytest.mark.parametrize( - "num_wires, num_hadamard, num_swap, num_ctrl_phase_shift", - [ - (1, 1, 0, 0), - (2, 2, 1, 1), - (3, 3, 1, 3), - (4, 4, 2, 6), - ], - ) - def test_resources_from_rep(self, num_wires, num_hadamard, num_swap, num_ctrl_phase_shift): - """Test that computing the resources from a compressed representation works""" - - hadamard = re.CompressedResourceOp(re.ResourceHadamard, {}) - swap = re.CompressedResourceOp(re.ResourceSWAP, {}) - ctrl_phase_shift = re.CompressedResourceOp(re.ResourceControlledPhaseShift, {}) - - expected = {hadamard: num_hadamard, swap: num_swap, ctrl_phase_shift: num_ctrl_phase_shift} - - rep = re.ResourceQFT.resource_rep(num_wires) - actual = rep.op_type.resources(**rep.params) - - assert actual == expected - - @pytest.mark.parametrize("num_wires", range(10)) - def test_tracking_name(self, num_wires): - """Test that the tracking name is correct.""" - assert re.ResourceQFT.tracking_name(num_wires + 1) == f"QFT({num_wires+1})" - - -class TestControlledSequence: - """Test the ResourceControlledSequence class""" - - @pytest.mark.parametrize( - "base_class, base_params, num_ctrl_wires, num_cseq", - [(re.ResourceHadamard, {}, 1, 1), (re.ResourceRX, {}, 3, 7)], - ) - def test_resources(self, base_class, base_params, num_ctrl_wires, num_cseq): - """Test the resources method returns the correct dictionary""" - resource_controlled_sequence = re.CompressedResourceOp( - re.ResourceControlled, - { - "base_class": base_class, - "base_params": base_params, - "num_ctrl_wires": 1, - "num_ctrl_values": 0, - "num_work_wires": 0, - }, - ) - expected = {resource_controlled_sequence: num_cseq} - - assert ( - re.ResourceControlledSequence.resources(base_class, base_params, num_ctrl_wires) - == expected - ) - - @pytest.mark.parametrize( - "base, control", - [(re.ResourceHadamard(3), [0, 1, 2]), (re.ResourceRX(0.25, 2), [0, 1])], - ) - def test_resource_params(self, base, control): - """Test that the resource params are correct""" - op = re.ResourceControlledSequence(base=base, control=control) - - assert op.resource_params == { - "base_class": type(base), - "base_params": base.resource_params, - "num_ctrl_wires": len(control), - } - - @pytest.mark.parametrize( - "base_class, base_params, num_ctrl_wires", - [(re.ResourceHadamard, {}, 1), (re.ResourceRX, {}, 3)], - ) - def test_resource_rep(self, base_class, base_params, num_ctrl_wires): - """Test the resource_rep returns the correct CompressedResourceOp""" - - expected = re.CompressedResourceOp( - re.ResourceControlledSequence, - { - "base_class": base_class, - "base_params": base_params, - "num_ctrl_wires": num_ctrl_wires, - }, - ) - assert expected == re.ResourceControlledSequence.resource_rep( - base_class, base_params, num_ctrl_wires - ) - - @pytest.mark.parametrize( - "base_class, base_params, num_ctrl_wires, num_cseq", - [(re.ResourceHadamard, {}, 1, 1), (re.ResourceRX, {}, 3, 7)], - ) - def test_resources_from_rep(self, base_class, base_params, num_ctrl_wires, num_cseq): - """Test that computing the resources from a compressed representation works""" - - resource_controlled_sequence = re.CompressedResourceOp( - re.ResourceControlled, - { - "base_class": base_class, - "base_params": base_params, - "num_ctrl_wires": 1, - "num_ctrl_values": 0, - "num_work_wires": 0, - }, - ) - - expected = {resource_controlled_sequence: num_cseq} - - rep = re.ResourceControlledSequence.resource_rep(base_class, base_params, num_ctrl_wires) - actual = rep.op_type.resources(**rep.params) - - assert actual == expected - - @pytest.mark.parametrize( - "base_class, base_params, num_ctrl_wires", - [(re.ResourceHadamard, {}, 1), (re.ResourceRX, {}, 3)], - ) - def test_tracking_name(self, base_class, base_params, num_ctrl_wires): - """Test that the tracking name is correct.""" - base_name = base_class.tracking_name(**base_params) - assert ( - re.ResourceControlledSequence.tracking_name(base_class, base_params, num_ctrl_wires) - == f"ControlledSequence({base_name}, {num_ctrl_wires})" - ) - - -class TestPhaseAdder: - """Test the ResourcePhaseAdder class""" - - @pytest.mark.parametrize( - "mod, num_x_wires", - [(8, 3), (7, 3)], - ) - def test_resources(self, mod, num_x_wires): - """Test the resources method returns the correct dictionary""" - if mod == 2**num_x_wires: - resource_phase_shift = re.CompressedResourceOp(re.ResourcePhaseShift, {}) - expected = {resource_phase_shift: num_x_wires} - assert re.ResourcePhaseAdder.resources(mod, num_x_wires) == expected - return - - qft = re.CompressedResourceOp(re.ResourceQFT, {"num_wires": num_x_wires}) - qft_dag = re.CompressedResourceOp( - re.ResourceAdjoint, - {"base_class": re.ResourceQFT, "base_params": {"num_wires": num_x_wires}}, - ) - - phase_shift = re.CompressedResourceOp(re.ResourcePhaseShift, {}) - phase_shift_dag = re.CompressedResourceOp( - re.ResourceAdjoint, {"base_class": re.ResourcePhaseShift, "base_params": {}} - ) - ctrl_phase_shift = re.CompressedResourceOp(re.ResourceControlledPhaseShift, {}) - - cnot = re.CompressedResourceOp(re.ResourceCNOT, {}) - multix = re.CompressedResourceOp( - re.ResourceMultiControlledX, - {"num_ctrl_wires": 1, "num_ctrl_values": 0, "num_work_wires": 1}, - ) - - expected = {} - expected[qft] = 2 - expected[qft_dag] = 2 - expected[phase_shift] = 2 * num_x_wires - expected[phase_shift_dag] = 2 * num_x_wires - expected[ctrl_phase_shift] = num_x_wires - expected[cnot] = 1 - expected[multix] = 1 - - assert re.ResourcePhaseAdder.resources(mod, num_x_wires) == expected - - @pytest.mark.parametrize( - "mod, x_wires", - [(8, [0, 1, 2]), (3, [0, 1])], - ) - def test_resource_params(self, mod, x_wires): - """Test that the resource params are correct""" - op = re.ResourcePhaseAdder(k=3, mod=mod, x_wires=x_wires, work_wire=[5]) - - assert op.resource_params == { - "mod": mod, - "num_x_wires": len(x_wires), - } - - @pytest.mark.parametrize( - "mod, num_x_wires", - [(8, 3), (3, 2)], - ) - def test_resource_rep(self, mod, num_x_wires): - """Test the resource_rep returns the correct CompressedResourceOp""" - - expected = re.CompressedResourceOp( - re.ResourcePhaseAdder, - { - "mod": mod, - "num_x_wires": num_x_wires, - }, - ) - assert expected == re.ResourcePhaseAdder.resource_rep(mod=mod, num_x_wires=num_x_wires) - - @pytest.mark.parametrize( - "mod, num_x_wires", - [(8, 3), (7, 3)], - ) - def test_resources_from_rep(self, mod, num_x_wires): - """Test that computing the resources from a compressed representation works""" - rep = re.ResourcePhaseAdder.resource_rep(mod=mod, num_x_wires=num_x_wires) - actual = rep.op_type.resources(**rep.params) - if mod == 2**num_x_wires: - resource_phase_shift = re.CompressedResourceOp(re.ResourcePhaseShift, {}) - expected = {resource_phase_shift: num_x_wires} - assert expected == actual - return - - qft = re.CompressedResourceOp(re.ResourceQFT, {"num_wires": num_x_wires}) - qft_dag = re.CompressedResourceOp( - re.ResourceAdjoint, - {"base_class": re.ResourceQFT, "base_params": {"num_wires": num_x_wires}}, - ) - - phase_shift = re.CompressedResourceOp(re.ResourcePhaseShift, {}) - phase_shift_dag = re.CompressedResourceOp( - re.ResourceAdjoint, {"base_class": re.ResourcePhaseShift, "base_params": {}} - ) - ctrl_phase_shift = re.CompressedResourceOp(re.ResourceControlledPhaseShift, {}) - - cnot = re.CompressedResourceOp(re.ResourceCNOT, {}) - multix = re.CompressedResourceOp( - re.ResourceMultiControlledX, - {"num_ctrl_wires": 1, "num_ctrl_values": 0, "num_work_wires": 1}, - ) - - expected = {} - expected[qft] = 2 - expected[qft_dag] = 2 - expected[phase_shift] = 2 * num_x_wires - expected[phase_shift_dag] = 2 * num_x_wires - expected[ctrl_phase_shift] = num_x_wires - expected[cnot] = 1 - expected[multix] = 1 - - assert actual == expected - - def test_tracking_name(self): - """Test that the tracking name is correct.""" - assert re.ResourcePhaseAdder.tracking_name() == "PhaseAdder" - - -class TestResourceMultiplier: - """Test the ResourceMultiplier class""" - - @pytest.mark.parametrize( - "mod, num_work_wires, num_x_wires", - [(7, 5, 3), (8, 5, 3)], - ) - def test_resources(self, mod, num_work_wires, num_x_wires): - """Test the resources method returns the correct dictionary""" - if mod == 2**num_x_wires: - num_aux_wires = num_x_wires - num_aux_swap = num_x_wires - else: - num_aux_wires = num_work_wires - 1 - num_aux_swap = num_aux_wires - 1 - - qft = re.CompressedResourceOp(re.ResourceQFT, {"num_wires": num_aux_wires}) - qft_dag = re.CompressedResourceOp( - re.ResourceAdjoint, - {"base_class": re.ResourceQFT, "base_params": {"num_wires": num_aux_wires}}, - ) - - sequence = re.CompressedResourceOp( - re.ResourceControlledSequence, - { - "base_class": re.ResourcePhaseAdder, - "base_params": {}, - "num_ctrl_wires": num_x_wires, - }, - ) - - sequence_dag = re.CompressedResourceOp( - re.ResourceAdjoint, - { - "base_class": re.ResourceControlledSequence, - "base_params": { - "base_class": re.ResourcePhaseAdder, - "base_params": {}, - "num_ctrl_wires": num_x_wires, - }, - }, - ) - - cnot = re.CompressedResourceOp(re.ResourceCNOT, {}) - - expected = {} - expected[qft] = 2 - expected[qft_dag] = 2 - expected[sequence] = 1 - expected[sequence_dag] = 1 - expected[cnot] = min(num_x_wires, num_aux_swap) - - assert re.ResourceMultiplier.resources(mod, num_work_wires, num_x_wires) == expected - - @pytest.mark.parametrize( - "k, mod, work_wires, x_wires", - [(4, 7, [3, 4, 5, 6, 7], [0, 1, 2]), (5, 8, [3, 4, 5, 6, 7], [0, 1, 2])], - ) - def test_resource_params(self, k, mod, work_wires, x_wires): - """Test that the resource params are correct""" - op = re.ResourceMultiplier( - k=k, - x_wires=x_wires, - mod=mod, - work_wires=work_wires, - ) - - assert op.resource_params == { - "mod": mod, - "num_work_wires": len(work_wires), - "num_x_wires": len(x_wires), - } - - @pytest.mark.parametrize( - "mod, num_work_wires, num_x_wires", - [(7, 5, 3), (8, 5, 3)], - ) - def test_resource_rep(self, mod, num_work_wires, num_x_wires): - """Test the resource_rep returns the correct CompressedResourceOp""" - - expected = re.CompressedResourceOp( - re.ResourceMultiplier, - { - "mod": mod, - "num_work_wires": num_work_wires, - "num_x_wires": num_x_wires, - }, - ) - assert expected == re.ResourceMultiplier.resource_rep(mod, num_work_wires, num_x_wires) - - @pytest.mark.parametrize( - "mod, num_work_wires, num_x_wires", - [(7, 5, 3), (8, 5, 3)], - ) - def test_resources_from_rep(self, mod, num_work_wires, num_x_wires): - """Test that computing the resources from a compressed representation works""" - - if mod == 2**num_x_wires: - num_aux_wires = num_x_wires - num_aux_swap = num_x_wires - else: - num_aux_wires = num_work_wires - 1 - num_aux_swap = num_aux_wires - 1 - - qft = re.CompressedResourceOp(re.ResourceQFT, {"num_wires": num_aux_wires}) - qft_dag = re.CompressedResourceOp( - re.ResourceAdjoint, - {"base_class": re.ResourceQFT, "base_params": {"num_wires": num_aux_wires}}, - ) - - sequence = re.CompressedResourceOp( - re.ResourceControlledSequence, - { - "base_class": re.ResourcePhaseAdder, - "base_params": {}, - "num_ctrl_wires": num_x_wires, - }, - ) - - sequence_dag = re.CompressedResourceOp( - re.ResourceAdjoint, - { - "base_class": re.ResourceControlledSequence, - "base_params": { - "base_class": re.ResourcePhaseAdder, - "base_params": {}, - "num_ctrl_wires": num_x_wires, - }, - }, - ) - - cnot = re.CompressedResourceOp(re.ResourceCNOT, {}) - - expected = {} - expected[qft] = 2 - expected[qft_dag] = 2 - expected[sequence] = 1 - expected[sequence_dag] = 1 - expected[cnot] = min(num_x_wires, num_aux_swap) - - rep = re.ResourceMultiplier.resource_rep(mod, num_work_wires, num_x_wires) - actual = rep.op_type.resources(**rep.params) - - assert actual == expected - - def test_tracking_name(self): - """Test that the tracking name is correct.""" - assert re.ResourceMultiplier.tracking_name() == "Multiplier" - - -class TestResourceModExp: - """Test the ResourceModExp class""" - - @pytest.mark.parametrize( - "mod, num_output_wires, num_work_wires, num_x_wires", - [(7, 5, 5, 3), (8, 5, 5, 3)], - ) - def test_resources(self, mod, num_output_wires, num_work_wires, num_x_wires): - """Test that the resources are correct""" - mult_resources = re.ResourceMultiplier._resource_decomp( - mod, num_work_wires, num_output_wires - ) - expected = {} - - for comp_rep, _ in mult_resources.items(): - new_rep = re.CompressedResourceOp( - re.ResourceControlled, - { - "base_class": comp_rep.op_type, - "base_params": comp_rep.params, - "num_ctrl_wires": 1, - "num_ctrl_values": 0, - "num_work_wires": 0, - }, - ) - - if comp_rep._name in ("QFT", "Adjoint(QFT)"): - expected[new_rep] = 1 - else: - expected[new_rep] = mult_resources[comp_rep] * ((2**num_x_wires) - 1) - - assert ( - re.ResourceModExp.resources(mod, num_output_wires, num_work_wires, num_x_wires) - == expected - ) - - @pytest.mark.parametrize( - "k, mod, work_wires, x_wires", - [(4, 7, [3, 4, 5, 6, 7], [0, 1, 2]), (5, 8, [3, 4, 5, 6, 7], [0, 1, 2])], - ) - def test_resource_params(self, k, mod, work_wires, x_wires): - """Test that the resource params are correct""" - op = re.ResourceMultiplier( - k=k, - x_wires=x_wires, - mod=mod, - work_wires=work_wires, - ) - - assert op.resource_params == { - "mod": mod, - "num_work_wires": len(work_wires), - "num_x_wires": len(x_wires), - } - - @pytest.mark.parametrize( - "mod, num_output_wires, num_work_wires, num_x_wires", - [(7, 5, 5, 3), (8, 5, 5, 3)], - ) - def test_resource_rep(self, mod, num_output_wires, num_work_wires, num_x_wires): - """Test the resource_rep returns the correct CompressedResourceOp""" - - expected = re.CompressedResourceOp( - re.ResourceModExp, - { - "mod": mod, - "num_output_wires": num_output_wires, - "num_work_wires": num_work_wires, - "num_x_wires": num_x_wires, - }, - ) - assert expected == re.ResourceModExp.resource_rep( - mod, num_output_wires, num_work_wires, num_x_wires - ) - - @pytest.mark.parametrize( - "mod, num_output_wires, num_work_wires, num_x_wires", - [(7, 5, 5, 3), (8, 5, 5, 3)], - ) - def test_resources_from_rep(self, num_output_wires, mod, num_work_wires, num_x_wires): - """Test that computing the resources from a compressed representation works""" - mult_resources = re.ResourceMultiplier._resource_decomp( - mod, num_work_wires, num_output_wires - ) - expected = {} - - for comp_rep, _ in mult_resources.items(): - new_rep = re.CompressedResourceOp( - re.ResourceControlled, - { - "base_class": comp_rep.op_type, - "base_params": comp_rep.params, - "num_ctrl_wires": 1, - "num_ctrl_values": 0, - "num_work_wires": 0, - }, - ) - - if comp_rep._name in ("QFT", "Adjoint(QFT)"): - expected[new_rep] = 1 - else: - expected[new_rep] = mult_resources[comp_rep] * ((2**num_x_wires) - 1) - - rep = re.ResourceModExp.resource_rep(mod, num_output_wires, num_work_wires, num_x_wires) - actual = rep.op_type.resources(**rep.params) - - assert actual == expected - - def test_tracking_name(self): - """Test that the tracking name is correct.""" - assert re.ResourceModExp.tracking_name() == "ModExp" - - -class TestResourceQROM: - """Test the ResourceQROM class""" - - @pytest.mark.parametrize( - "num_bitstrings, num_bit_flips, num_control_wires, num_work_wires, size_bitstring, clean", - [(4, 2, 2, 3, 3, True), (4, 5, 2, 3, 3, False), (4, 5, 0, 3, 3, False)], - ) - def test_resources( - self, - num_bitstrings, - num_bit_flips, - num_control_wires, - num_work_wires, - size_bitstring, - clean, - ): - """Test that the resources are correct""" - expected = {} - x = re.CompressedResourceOp(re.ResourceX, {}) - - if num_control_wires == 0: - expected[x] = num_bit_flips - assert ( - re.ResourceQROM.resources( - num_bitstrings, - num_bit_flips, - num_control_wires, - num_work_wires, - size_bitstring, - clean, - ) - == expected - ) - return - - cnot = re.CompressedResourceOp(re.ResourceCNOT, {}) - hadamard = re.CompressedResourceOp(re.ResourceHadamard, {}) - - num_parallel_computations = (num_work_wires + size_bitstring) // size_bitstring - num_parallel_computations = min(num_parallel_computations, num_bitstrings) - - num_swap_wires = math.floor(math.log2(num_parallel_computations)) - num_select_wires = math.ceil(math.log2(math.ceil(num_bitstrings / (2**num_swap_wires)))) - assert num_swap_wires + num_select_wires <= num_control_wires - - swap_clean_prefactor = 1 - select_clean_prefactor = 1 - - if clean: - expected[hadamard] = 2 * size_bitstring - swap_clean_prefactor = 4 - select_clean_prefactor = 2 - - # SELECT cost: - expected[cnot] = num_bit_flips # each unitary in the select is just a CNOT - - multi_x = re.CompressedResourceOp( - re.ResourceMultiControlledX, - { - "num_ctrl_wires": num_select_wires, - "num_ctrl_values": 0, - "num_work_wires": 0, - }, - ) - - num_total_ctrl_possibilities = 2**num_select_wires - expected[multi_x] = select_clean_prefactor * ( - 2 * num_total_ctrl_possibilities # two applications targetting the aux qubit - ) - num_zero_controls = (2 * num_total_ctrl_possibilities * num_select_wires) // 2 - expected[x] = select_clean_prefactor * ( - num_zero_controls * 2 # conjugate 0 controls on the multi-qubit x gates from above - ) - # SWAP cost: - ctrl_swap = re.CompressedResourceOp(re.ResourceCSWAP, {}) - expected[ctrl_swap] = swap_clean_prefactor * ((2**num_swap_wires) - 1) * size_bitstring - - assert ( - re.ResourceQROM.resources( - num_bitstrings, - num_bit_flips, - num_control_wires, - num_work_wires, - size_bitstring, - clean, - ) - == expected - ) - - @pytest.mark.parametrize( - "bitstrings, control_wires, work_wires, target_wires, clean", - [ - (["000", "001", "010", "100"], [0, 1], [2, 3, 4], [5, 6, 7], True), - (["010", "111", "110", "000"], [0, 1], [2, 3, 4], [5, 6, 7], False), - ], - ) - def test_resource_params(self, bitstrings, control_wires, work_wires, target_wires, clean): - """Test that the resource params are correct""" - op = re.ResourceQROM( - bitstrings=bitstrings, - control_wires=control_wires, - target_wires=work_wires, - work_wires=target_wires, - clean=clean, - ) - - num_bitstrings = len(bitstrings) - - num_bit_flips = 0 - for bit_string in bitstrings: - num_bit_flips += bit_string.count("1") - - num_work_wires = len(work_wires) - size_bitstring = len(target_wires) - num_control_wires = len(control_wires) - - assert op.resource_params == { - "num_bitstrings": num_bitstrings, - "num_bit_flips": num_bit_flips, - "num_control_wires": num_control_wires, - "num_work_wires": num_work_wires, - "size_bitstring": size_bitstring, - "clean": clean, - } - - @pytest.mark.parametrize( - "num_bitstrings, num_bit_flips, num_control_wires, num_work_wires, size_bitstring, clean", - [(4, 2, 2, 3, 3, True), (4, 5, 2, 3, 3, False), (4, 5, 0, 3, 3, False)], - ) - def test_resource_rep( - self, - num_bitstrings, - num_bit_flips, - num_control_wires, - num_work_wires, - size_bitstring, - clean, - ): - """Test the resource_rep returns the correct CompressedResourceOp""" - - expected = re.CompressedResourceOp( - re.ResourceQROM, - { - "num_bitstrings": num_bitstrings, - "num_bit_flips": num_bit_flips, - "num_control_wires": num_control_wires, - "num_work_wires": num_work_wires, - "size_bitstring": size_bitstring, - "clean": clean, - }, - ) - assert expected == re.ResourceQROM.resource_rep( - num_bitstrings, num_bit_flips, num_control_wires, num_work_wires, size_bitstring, clean - ) - - @pytest.mark.parametrize( - "num_bitstrings, num_bit_flips, num_control_wires, num_work_wires, size_bitstring, clean", - [(4, 2, 2, 3, 3, True), (4, 5, 2, 3, 3, False), (4, 5, 0, 3, 3, False)], - ) - def test_resources_from_rep( - self, - num_bitstrings, - num_bit_flips, - num_control_wires, - num_work_wires, - size_bitstring, - clean, - ): - """Test that computing the resources from a compressed representation works""" - expected = {} - rep = re.ResourceQROM.resource_rep( - num_bitstrings, num_bit_flips, num_control_wires, num_work_wires, size_bitstring, clean - ) - actual = rep.op_type.resources(**rep.params) - - x = re.CompressedResourceOp(re.ResourceX, {}) - - if num_control_wires == 0: - expected[x] = num_bit_flips - assert actual == expected - return - - cnot = re.CompressedResourceOp(re.ResourceCNOT, {}) - hadamard = re.CompressedResourceOp(re.ResourceHadamard, {}) - - num_parallel_computations = (num_work_wires + size_bitstring) // size_bitstring - num_parallel_computations = min(num_parallel_computations, num_bitstrings) - - num_swap_wires = math.floor(math.log2(num_parallel_computations)) - num_select_wires = math.ceil(math.log2(math.ceil(num_bitstrings / (2**num_swap_wires)))) - assert num_swap_wires + num_select_wires <= num_control_wires - - swap_clean_prefactor = 1 - select_clean_prefactor = 1 - - if clean: - expected[hadamard] = 2 * size_bitstring - swap_clean_prefactor = 4 - select_clean_prefactor = 2 - - # SELECT cost: - expected[cnot] = num_bit_flips # each unitary in the select is just a CNOT - - multi_x = re.CompressedResourceOp( - re.ResourceMultiControlledX, - { - "num_ctrl_wires": num_select_wires, - "num_ctrl_values": 0, - "num_work_wires": 0, - }, - ) - - num_total_ctrl_possibilities = 2**num_select_wires - expected[multi_x] = select_clean_prefactor * ( - 2 * num_total_ctrl_possibilities # two applications targetting the aux qubit - ) - num_zero_controls = (2 * num_total_ctrl_possibilities * num_select_wires) // 2 - expected[x] = select_clean_prefactor * ( - num_zero_controls * 2 # conjugate 0 controls on the multi-qubit x gates from above - ) - # SWAP cost: - ctrl_swap = re.CompressedResourceOp(re.ResourceCSWAP, {}) - expected[ctrl_swap] = swap_clean_prefactor * ((2**num_swap_wires) - 1) * size_bitstring - - assert actual == expected - - def test_tracking_name(self): - """Test that the tracking name is correct.""" - assert re.ResourceQROM.tracking_name() == "QROM" - - -class TestResourceStatePrep: - """Test the ResourceStatePrep class""" - - @pytest.mark.parametrize( - "num_wires", - [(4), (5), (6)], - ) - def test_resources(self, num_wires): - """Test that the resources are correct""" - expected = { - re.CompressedResourceOp( - re.ResourceMottonenStatePreparation, {"num_wires": num_wires} - ): 1 - } - - assert re.ResourceStatePrep.resources(num_wires) == expected - - @pytest.mark.parametrize( - "state, wires", - [ - ( - np.array([1 / 2, 1 / 2, 1 / 2, 1 / 2]), - range(2), - ), - ], - ) - def test_resource_params(self, state, wires): - """Test that the resource params are correct""" - op = re.ResourceStatePrep(state=state, wires=wires, normalize=True) - - assert op.resource_params == {"num_wires": len(wires)} - - @pytest.mark.parametrize( - "num_wires", - [(4), (5), (6)], - ) - def test_resource_rep(self, num_wires): - """Test the resource_rep returns the correct CompressedResourceOp""" - - expected = re.CompressedResourceOp( - re.ResourceStatePrep, - {"num_wires": num_wires}, - ) - assert expected == re.ResourceStatePrep.resource_rep(num_wires) - - @pytest.mark.parametrize( - "num_wires", - [(4), (5), (6)], - ) - def test_resources_from_rep(self, num_wires): - """Test that computing the resources from a compressed representation works""" - rep = re.ResourceStatePrep.resource_rep(num_wires) - actual = rep.op_type.resources(**rep.params) - - expected = { - re.CompressedResourceOp( - re.ResourceMottonenStatePreparation, {"num_wires": num_wires} - ): 1 - } - - assert actual == expected - - @pytest.mark.parametrize( - "num_wires", - [(4), (5), (6)], - ) - def test_tracking_name(self, num_wires): - """Test that the tracking name is correct.""" - assert re.ResourceStatePrep.tracking_name(num_wires) == f"StatePrep({num_wires})" - - -class TestResourceAmplitudeAmplification: - """Test the ResourceAmplitudeAmplification class""" - - @pytest.mark.parametrize( - "U_op, U_params, O_op,O_params,iters,num_ref_wires,fixed_point", - [(re.ResourceHadamard, {}, re.ResourceRX, {}, 5, 3, True)], - ) - def test_resources(self, U_op, U_params, O_op, O_params, iters, num_ref_wires, fixed_point): - """Test that the resources are correct""" - expected = {} - reflection = re.ResourceReflection.resource_rep( - base_class=U_op, base_params=U_params, num_ref_wires=num_ref_wires - ) - - if not fixed_point: - oracles = re.CompressedResourceOp(O_op, params=O_params) - expected[oracles] = iters - expected[reflection] = iters - - assert ( - re.ResourceAmplitudeAmplification.resources( - U_op, - U_params, - O_op, - O_params, - iters, - num_ref_wires, - fixed_point, - ) - == expected - ) - iters = iters // 2 - ctrl = re.ResourceControlled.resource_rep( - base_class=O_op, - base_params=O_params, - num_ctrl_wires=1, - num_ctrl_values=0, - num_work_wires=0, - ) - phase_shift = re.ResourcePhaseShift.resource_rep() - hadamard = re.ResourceHadamard.resource_rep() - - expected[ctrl] = iters * 2 - expected[phase_shift] = iters - expected[hadamard] = iters * 4 - expected[reflection] = iters - - assert ( - re.ResourceAmplitudeAmplification.resources( - U_op, U_params, O_op, O_params, 5, num_ref_wires, fixed_point - ) - == expected - ) - - def test_resource_params(self): - """Test that the resource params are correct""" - - @qml.prod - def generator(wires): - for wire in wires: - qml.Hadamard(wires=wire) - - U = generator(wires=range(3)) - O = qml.FlipSign(2, wires=range(3)) - - expected = { - "U_op": type(U), - "U_params": {}, - "O_op": type(O), - "O_params": {}, - "iters": 5, - "num_ref_wires": 3, - "fixed_point": True, - } - - op = re.ResourceAmplitudeAmplification(U=U, O=O, iters=5, fixed_point=True, work_wire=3) - - assert op.resource_params == expected - - @pytest.mark.parametrize( - "U_op, U_params, O_op,O_params,iters, num_ref_wires,fixed_point", - [(re.ResourceHadamard, {}, re.ResourceRX, {}, 5, 3, True)], - ) - def test_resource_rep(self, U_op, U_params, O_op, O_params, iters, num_ref_wires, fixed_point): - """Test the resource_rep returns the correct CompressedResourceOp""" - - expected = re.CompressedResourceOp( - re.ResourceAmplitudeAmplification, - { - "U_op": U_op, - "U_params": U_params, - "O_op": O_op, - "O_params": O_params, - "iters": iters, - "num_ref_wires": num_ref_wires, - "fixed_point": fixed_point, - }, - ) - assert expected == re.ResourceAmplitudeAmplification.resource_rep( - U_op, U_params, O_op, O_params, iters, num_ref_wires, fixed_point - ) - - @pytest.mark.parametrize( - "U_op, U_params, O_op,O_params,iters,num_ref_wires,fixed_point", - [(re.ResourceHadamard, {}, re.ResourceRX, {}, 5, 3, True)], - ) - def test_resources_from_rep( - self, U_op, U_params, O_op, O_params, iters, num_ref_wires, fixed_point - ): - """Test that computing the resources from a compressed representation works""" - rep = re.ResourceAmplitudeAmplification.resource_rep( - U_op, U_params, O_op, O_params, iters, num_ref_wires, fixed_point - ) - actual = rep.op_type.resources(**rep.params) - - expected = {} - reflection = re.ResourceReflection.resource_rep( - base_class=U_op, base_params=U_params, num_ref_wires=num_ref_wires - ) - - if not fixed_point: - oracles = re.CompressedResourceOp(O_op, params=O_params) - expected[oracles] = iters - expected[reflection] = iters - - assert ( - re.ResourceAmplitudeAmplification.resources( - U_op, - U_params, - O_op, - O_params, - iters, - num_ref_wires, - fixed_point, - ) - == expected - ) - iters = iters // 2 - ctrl = re.ResourceControlled.resource_rep( - base_class=O_op, - base_params=O_params, - num_ctrl_wires=1, - num_ctrl_values=0, - num_work_wires=0, - ) - phase_shift = re.ResourcePhaseShift.resource_rep() - hadamard = re.ResourceHadamard.resource_rep() - - expected[ctrl] = iters * 2 - expected[phase_shift] = iters - expected[hadamard] = iters * 4 - expected[reflection] = iters - - assert actual == expected - - @pytest.mark.parametrize( - "U_op, U_params, O_op,O_params,iters,num_ref_wires,fixed_point", - [(re.ResourceHadamard, {}, re.ResourceRX, {}, 5, 3, True)], - ) - def test_tracking_name(self, U_op, U_params, O_op, O_params, iters, num_ref_wires, fixed_point): - """Test that the tracking name is correct.""" - assert ( - re.ResourceAmplitudeAmplification.tracking_name( - U_op, U_params, O_op, O_params, iters, num_ref_wires, fixed_point - ) - == "AmplitudeAmplification" - ) diff --git a/pennylane/labs/tests/resource_estimation/test_resource_container.py b/pennylane/labs/tests/resource_estimation/test_resource_container.py deleted file mode 100644 index 4c3983929df..00000000000 --- a/pennylane/labs/tests/resource_estimation/test_resource_container.py +++ /dev/null @@ -1,392 +0,0 @@ -# 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. -""" -Test base Resource class and its associated methods -""" -# pylint:disable=protected-access, no-self-use, too-few-public-methods -import copy -from collections import defaultdict - -import pytest - -import pennylane as qml -from pennylane.labs.resource_estimation.resource_container import ( - CompressedResourceOp, - Resources, - _combine_dict, - _scale_dict, - add_in_parallel, - add_in_series, - mul_in_parallel, - mul_in_series, - substitute, -) -from pennylane.labs.resource_estimation.resource_operator import ResourceOperator -from pennylane.operation import Operator - - -class ResourceDummyX(Operator, ResourceOperator): - """Dummy testing class representing X gate""" - - -class ResourceDummyQFT(Operator, ResourceOperator): - """Dummy testing class representing QFT gate""" - - -class ResourceDummyQSVT(Operator, ResourceOperator): - """Dummy testing class representing QSVT gate""" - - -class ResourceDummyTrotterProduct(Operator, ResourceOperator): - """Dummy testing class representing TrotterProduct gate""" - - -class ResourceDummyAdjoint(Operator, ResourceOperator): - """Dummy testing class representing the Adjoint symbolic operator""" - - -class TestCompressedResourceOp: - """Testing the methods and attributes of the CompressedResourceOp class""" - - test_hamiltonian = qml.dot([1, -1, 0.5], [qml.X(0), qml.Y(1), qml.Z(0) @ qml.Z(1)]) - compressed_ops_and_params_lst = ( - ("DummyX", ResourceDummyX, {"num_wires": 1}, None), - ("DummyQFT", ResourceDummyQFT, {"num_wires": 5}, None), - ("DummyQSVT", ResourceDummyQSVT, {"num_wires": 3, "num_angles": 5}, None), - ( - "DummyTrotterProduct", - ResourceDummyTrotterProduct, - {"Hamiltonian": test_hamiltonian, "num_steps": 5, "order": 2}, - None, - ), - ("X", ResourceDummyX, {"num_wires": 1}, "X"), - ) - - compressed_op_reprs = ( - "DummyX", - "DummyQFT", - "DummyQSVT", - "DummyTrotterProduct", - "X", - ) - - @pytest.mark.parametrize("name, op_type, parameters, name_param", compressed_ops_and_params_lst) - def test_init(self, name, op_type, parameters, name_param): - """Test that we can correctly instantiate CompressedResourceOp""" - cr_op = CompressedResourceOp(op_type, parameters, name=name_param) - - assert cr_op._name == name - assert cr_op.op_type is op_type - assert cr_op.params == parameters - assert cr_op._hashable_params == tuple(sorted((str(k), v) for k, v in parameters.items())) - - def test_hash(self): - """Test that the hash method behaves as expected""" - CmprssedQSVT1 = CompressedResourceOp(ResourceDummyQSVT, {"num_wires": 3, "num_angles": 5}) - CmprssedQSVT2 = CompressedResourceOp(ResourceDummyQSVT, {"num_wires": 3, "num_angles": 5}) - Other = CompressedResourceOp(ResourceDummyQFT, {"num_wires": 3}) - - assert hash(CmprssedQSVT1) == hash(CmprssedQSVT1) # compare same object - assert hash(CmprssedQSVT1) == hash(CmprssedQSVT2) # compare identical instance - assert hash(CmprssedQSVT1) != hash(Other) - - # test dictionary as parameter - CmprssedAdjoint1 = CompressedResourceOp( - ResourceDummyAdjoint, {"base_class": ResourceDummyQFT, "base_params": {"num_wires": 1}} - ) - CmprssedAdjoint2 = CompressedResourceOp( - ResourceDummyAdjoint, {"base_class": ResourceDummyQFT, "base_params": {"num_wires": 1}} - ) - Other = CompressedResourceOp( - ResourceDummyAdjoint, {"base_class": ResourceDummyQFT, "base_params": {"num_wires": 2}} - ) - - assert hash(CmprssedAdjoint1) == hash(CmprssedAdjoint1) - assert hash(CmprssedAdjoint1) == hash(CmprssedAdjoint2) - assert hash(CmprssedAdjoint1) != hash(Other) - - def test_equality(self): - """Test that the equality methods behaves as expected""" - CmprssedQSVT1 = CompressedResourceOp(ResourceDummyQSVT, {"num_wires": 3, "num_angles": 5}) - CmprssedQSVT2 = CompressedResourceOp(ResourceDummyQSVT, {"num_wires": 3, "num_angles": 5}) - CmprssedQSVT3 = CompressedResourceOp(ResourceDummyQSVT, {"num_angles": 5, "num_wires": 3}) - Other = CompressedResourceOp(ResourceDummyQFT, {"num_wires": 3}) - - assert CmprssedQSVT1 == CmprssedQSVT2 # compare identical instance - assert CmprssedQSVT1 == CmprssedQSVT3 # compare swapped parameters - assert CmprssedQSVT1 != Other - - @pytest.mark.parametrize("args, repr", zip(compressed_ops_and_params_lst, compressed_op_reprs)) - def test_repr(self, args, repr): - """Test that the repr method behaves as expected.""" - _, op_type, parameters, name_param = args - cr_op = CompressedResourceOp(op_type, parameters, name=name_param) - - assert str(cr_op) == repr - - -class TestResources: - """Test the methods and attributes of the Resource class""" - - resource_quantities = ( - Resources(), - Resources(5, 0, defaultdict(int, {})), - Resources(1, 3, defaultdict(int, {"Hadamard": 1, "PauliZ": 2})), - Resources(4, 2, defaultdict(int, {"Hadamard": 1, "CNOT": 1})), - ) - - resource_parameters = ( - (0, 0, defaultdict(int, {})), - (5, 0, defaultdict(int, {})), - (1, 3, defaultdict(int, {"Hadamard": 1, "PauliZ": 2})), - (4, 2, defaultdict(int, {"Hadamard": 1, "CNOT": 1})), - ) - - @pytest.mark.parametrize("r, attribute_tup", zip(resource_quantities, resource_parameters)) - def test_init(self, r, attribute_tup): - """Test that the Resource class is instantiated as expected.""" - num_wires, num_gates, gate_types = attribute_tup - - assert r.num_wires == num_wires - assert r.num_gates == num_gates - assert r.gate_types == gate_types - - expected_results_add_series = ( - Resources(2, 6, defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1})), - Resources(5, 6, defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1})), - Resources( - 2, 9, defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 2, "PauliZ": 2}) - ), - Resources(4, 8, defaultdict(int, {"RZ": 2, "CNOT": 2, "RY": 2, "Hadamard": 2})), - ) - - @pytest.mark.parametrize("in_place", (False, True)) - @pytest.mark.parametrize( - "resource_obj, expected_res_obj", zip(resource_quantities, expected_results_add_series) - ) - def test_add_in_series(self, resource_obj, expected_res_obj, in_place): - """Test the add_in_series function works with Resoruces""" - resource_obj = copy.deepcopy(resource_obj) - other_obj = Resources( - num_wires=2, - num_gates=6, - gate_types=defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1}), - ) - - resultant_obj = add_in_series(resource_obj, other_obj, in_place=in_place) - assert resultant_obj == expected_res_obj - - if in_place: - assert resultant_obj is resource_obj - - expected_results_add_parallel = ( - Resources(2, 6, defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1})), - Resources(7, 6, defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1})), - Resources( - 3, 9, defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 2, "PauliZ": 2}) - ), - Resources(6, 8, defaultdict(int, {"RZ": 2, "CNOT": 2, "RY": 2, "Hadamard": 2})), - ) - - @pytest.mark.parametrize("in_place", (False, True)) - @pytest.mark.parametrize( - "resource_obj, expected_res_obj", zip(resource_quantities, expected_results_add_parallel) - ) - def test_add_in_parallel(self, resource_obj, expected_res_obj, in_place): - """Test the add_in_parallel function works with Resoruces""" - resource_obj = copy.deepcopy(resource_obj) - other_obj = Resources( - num_wires=2, - num_gates=6, - gate_types=defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1}), - ) - - resultant_obj = add_in_parallel(resource_obj, other_obj, in_place=in_place) - assert resultant_obj == expected_res_obj - - if in_place: - assert resultant_obj is resource_obj - - expected_results_mul_series = ( - Resources( - num_wires=2, - num_gates=6, - gate_types=defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1}), - ), - Resources( - num_wires=2, - num_gates=12, - gate_types=defaultdict(int, {"RZ": 4, "CNOT": 2, "RY": 4, "Hadamard": 2}), - ), - Resources( - num_wires=2, - num_gates=18, - gate_types=defaultdict(int, {"RZ": 6, "CNOT": 3, "RY": 6, "Hadamard": 3}), - ), - Resources( - num_wires=2, - num_gates=30, - gate_types=defaultdict(int, {"RZ": 10, "CNOT": 5, "RY": 10, "Hadamard": 5}), - ), - ) - - @pytest.mark.parametrize("in_place", (False, True)) - @pytest.mark.parametrize( - "scalar, expected_res_obj", zip((1, 2, 3, 5), expected_results_mul_series) - ) - def test_mul_in_series(self, scalar, expected_res_obj, in_place): - """Test the mul_in_series function works with Resoruces""" - resource_obj = Resources( - num_wires=2, - num_gates=6, - gate_types=defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1}), - ) - - resultant_obj = mul_in_series(resource_obj, scalar, in_place=in_place) - assert resultant_obj == expected_res_obj - - if in_place: - assert resultant_obj is resource_obj - assert True - - expected_results_mul_parallel = ( - Resources( - num_wires=2, - num_gates=6, - gate_types=defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1}), - ), - Resources( - num_wires=4, - num_gates=12, - gate_types=defaultdict(int, {"RZ": 4, "CNOT": 2, "RY": 4, "Hadamard": 2}), - ), - Resources( - num_wires=6, - num_gates=18, - gate_types=defaultdict(int, {"RZ": 6, "CNOT": 3, "RY": 6, "Hadamard": 3}), - ), - Resources( - num_wires=10, - num_gates=30, - gate_types=defaultdict(int, {"RZ": 10, "CNOT": 5, "RY": 10, "Hadamard": 5}), - ), - ) - - @pytest.mark.parametrize("in_place", (False, True)) - @pytest.mark.parametrize( - "scalar, expected_res_obj", zip((1, 2, 3, 5), expected_results_mul_parallel) - ) - def test_mul_in_parallel(self, scalar, expected_res_obj, in_place): - """Test the mul_in_parallel function works with Resoruces""" - resource_obj = Resources( - num_wires=2, - num_gates=6, - gate_types=defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1}), - ) - - resultant_obj = mul_in_parallel(resource_obj, scalar, in_place=in_place) - assert resultant_obj == expected_res_obj - - if in_place: - assert resultant_obj is resource_obj - assert True - - test_str_data = ( - ("wires: 0\n" + "gates: 0\n" + "gate_types:\n" + "{}"), - ("wires: 5\n" + "gates: 0\n" + "gate_types:\n" + "{}"), - ("wires: 1\n" + "gates: 3\n" + "gate_types:\n" + "{'Hadamard': 1, 'PauliZ': 2}"), - ("wires: 4\n" + "gates: 2\n" + "gate_types:\n" + "{'Hadamard': 1, 'CNOT': 1}"), - ) - - @pytest.mark.parametrize("r, rep", zip(resource_quantities, test_str_data)) - def test_str(self, r, rep): - """Test the string representation of a Resources instance.""" - assert str(r) == rep - - @pytest.mark.parametrize("r, rep", zip(resource_quantities, test_str_data)) - def test_ipython_display(self, r, rep, capsys): - """Test that the ipython display prints the string representation of a Resources instance.""" - r._ipython_display_() # pylint: disable=protected-access - captured = capsys.readouterr() - assert rep in captured.out - - gate_names = ("RX", "RZ") - expected_results_sub = ( - Resources( - num_wires=2, - num_gates=6, - gate_types=defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1}), - ), - Resources( - num_wires=2, - num_gates=14, - gate_types=defaultdict(int, {"RX": 10, "CNOT": 1, "RY": 2, "Hadamard": 1}), - ), - ) - - @pytest.mark.parametrize("gate_name, expected_res_obj", zip(gate_names, expected_results_sub)) - def test_substitute(self, gate_name, expected_res_obj): - """Test the substitute function""" - resource_obj = Resources( - num_wires=2, - num_gates=6, - gate_types=defaultdict(int, {"RZ": 2, "CNOT": 1, "RY": 2, "Hadamard": 1}), - ) - - sub_obj = Resources( - num_wires=1, - num_gates=5, - gate_types=defaultdict(int, {"RX": 5}), - ) - - resultant_obj1 = substitute(resource_obj, gate_name, sub_obj, in_place=False) - assert resultant_obj1 == expected_res_obj - - resultant_obj2 = substitute(resource_obj, gate_name, sub_obj, in_place=True) - assert resultant_obj2 == expected_res_obj - assert resultant_obj2 is resource_obj - - -@pytest.mark.parametrize("in_place", [False, True]) -def test_combine_dict(in_place): - """Test that we can combine dictionaries as expected.""" - d1 = defaultdict(int, {"a": 2, "b": 4, "c": 6}) - d2 = defaultdict(int, {"a": 1, "b": 2, "d": 3}) - - result = _combine_dict(d1, d2, in_place=in_place) - expected = defaultdict(int, {"a": 3, "b": 6, "c": 6, "d": 3}) - - assert result == expected - - if in_place: - assert result is d1 - else: - assert result is not d1 - - -@pytest.mark.parametrize("scalar", (1, 2, 3)) -@pytest.mark.parametrize("in_place", (False, True)) -def test_scale_dict(scalar, in_place): - """Test that we can scale the values of a dictionary as expected.""" - d1 = defaultdict(int, {"a": 2, "b": 4, "c": 6}) - - expected = defaultdict(int, {k: scalar * v for k, v in d1.items()}) - result = _scale_dict(d1, scalar, in_place=in_place) - - assert result == expected - - if in_place: - assert result is d1 - else: - assert result is not d1 diff --git a/pennylane/labs/tests/resource_estimation/test_resource_operator.py b/pennylane/labs/tests/resource_estimation/test_resource_operator.py deleted file mode 100644 index 68406d6723a..00000000000 --- a/pennylane/labs/tests/resource_estimation/test_resource_operator.py +++ /dev/null @@ -1,132 +0,0 @@ -# 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. -""" -Test the abstract ResourceOperator class -""" -import pytest - -import pennylane.labs.resource_estimation as re - -# pylint: disable=abstract-class-instantiated,arguments-differ,missing-function-docstring,too-few-public-methods - - -def test_abstract_resource_decomp(): - """Test that the _resource_decomp method is abstract.""" - - class DummyClass(re.ResourceOperator): - """Dummy class for testing""" - - @property - def resource_params(self): - return - - @staticmethod - def resource_rep(): - return - - with pytest.raises( - TypeError, - match="Can't instantiate abstract class DummyClass with abstract method _resource_decomp", - ): - DummyClass() - - -def test_abstract_resource_params(): - """Test that the resource_params method is abstract""" - - class DummyClass(re.ResourceOperator): - """Dummy class for testing""" - - @staticmethod - def _resource_decomp(): - return - - def resource_rep(self): - return - - with pytest.raises( - TypeError, - match="Can't instantiate abstract class DummyClass with abstract method resource_params", - ): - DummyClass() - - -def test_abstract_resource_rep(): - """Test that the resource_rep method is abstract""" - - class DummyClass(re.ResourceOperator): - """Dummy class for testing""" - - @staticmethod - def _resource_decomp(): - return - - @property - def resource_params(self): - return - - with pytest.raises( - TypeError, - match="Can't instantiate abstract class DummyClass with abstract method resource_rep", - ): - DummyClass() - - -def test_set_resources(): - """Test that the resources method can be overriden""" - - class DummyClass(re.ResourceOperator): - """Dummy class for testing""" - - @property - def resource_params(self): - return - - @staticmethod - def resource_rep(): - return - - @staticmethod - def _resource_decomp(): - return - - dummy = DummyClass() - DummyClass.set_resources(lambda _: 5) - assert DummyClass.resources(10) == 5 - - -def test_resource_rep_from_op(): - """Test that the resource_rep_from_op method is the composition of resource_params and resource_rep""" - - class DummyClass(re.ResourceQFT, re.ResourceOperator): - """Dummy class for testing""" - - @staticmethod - def _resource_decomp(): - return - - @property - def resource_params(self): - return {"foo": 1, "bar": 2} - - @classmethod - def resource_rep(cls, foo, bar): - return re.CompressedResourceOp(cls, {"foo": foo, "bar": bar}) - - @staticmethod - def tracking_name(foo, bar): - return f"DummyClass({foo}, {bar})" - - op = DummyClass(wires=[1, 2, 3]) - assert op.resource_rep_from_op() == op.__class__.resource_rep(**op.resource_params) diff --git a/pennylane/labs/tests/resource_estimation/test_resource_tracking.py b/pennylane/labs/tests/resource_estimation/test_resource_tracking.py deleted file mode 100644 index 27fdbf46ff1..00000000000 --- a/pennylane/labs/tests/resource_estimation/test_resource_tracking.py +++ /dev/null @@ -1,357 +0,0 @@ -# Copyright 2025 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. -"""Test the core resource tracking pipeline.""" -from collections import defaultdict -from copy import copy - -# pylint:disable=protected-access, no-self-use -import pytest - -import pennylane as qml -import pennylane.labs.resource_estimation as re -from pennylane.labs.resource_estimation.resource_tracking import ( - DefaultGateSet, - _clean_gate_counts, - _counts_from_compressed_res_op, - _operations_to_compressed_reps, - _StandardGateSet, - get_resources, - resource_config, - resources_from_operation, - resources_from_qfunc, - resources_from_tape, -) - - -class DummyOperation(qml.operation.Operation): - """Dummy class to test _operations_to_compressed_reps function.""" - - def __init__(self, wires=None): - super().__init__(wires=wires) - - def decomposition(self): - decomp = [ - re.ResourceHadamard(wires=self.wires[0]), - re.ResourceHadamard(wires=self.wires[1]), - re.ResourceCNOT(wires=[self.wires[0], self.wires[1]]), - ] - - return decomp - - -class TestGetResources: - """Test the core resource tracking pipeline""" - - compressed_rep_data = ( - ( - [ - re.ResourceHadamard(0), - re.ResourceRX(1.23, 1), - re.ResourceCNOT(wires=[1, 2]), - ], - [ - re.CompressedResourceOp(re.ResourceHadamard, {}), - re.CompressedResourceOp(re.ResourceRX, {}), - re.CompressedResourceOp(re.ResourceCNOT, {}), - ], - ), - ( - [ - re.ResourceQFT(wires=[1, 2, 3]), - re.ResourceIdentity(wires=[1, 2, 3]), - re.ResourceRot(1.23, 0.45, -6, wires=0), - re.ResourceQFT(wires=[1, 2]), - ], - [ - re.CompressedResourceOp(re.ResourceQFT, {"num_wires": 3}), - re.CompressedResourceOp(re.ResourceIdentity, {}), - re.CompressedResourceOp(re.ResourceRot, {}), - re.CompressedResourceOp(re.ResourceQFT, {"num_wires": 2}), - ], - ), - ( - [ - re.ResourceQFT(wires=[0, 1]), - DummyOperation(wires=["a", "b"]), - re.ResourceRY(-0.5, wires=1), - ], - [ - re.CompressedResourceOp(re.ResourceQFT, {"num_wires": 2}), - re.CompressedResourceOp(re.ResourceHadamard, {}), - re.CompressedResourceOp(re.ResourceHadamard, {}), - re.CompressedResourceOp(re.ResourceCNOT, {}), - re.CompressedResourceOp(re.ResourceRY, {}), - ], - ), # Test decomposition logic - ) - - @pytest.mark.parametrize("ops_lst, compressed_reps", compressed_rep_data) - def test_operations_to_compressed_reps(self, ops_lst, compressed_reps): - """Test that we can transform a list of operations into compressed reps""" - computed_compressed_reps = _operations_to_compressed_reps(ops_lst) - for computed_cr, expected_cr in zip(computed_compressed_reps, compressed_reps): - assert computed_cr == expected_cr - - compressed_rep_counts = ( - ( - re.ResourceHadamard(wires=0).resource_rep_from_op(), - defaultdict(int, {re.CompressedResourceOp(re.ResourceHadamard, {}): 1}), - ), - ( - re.ResourceRX(1.23, wires=0).resource_rep_from_op(), - defaultdict(int, {re.CompressedResourceOp(re.ResourceT, {}): 17}), - ), - ( - re.ResourceIdentity(wires=[1, 2, 3]).resource_rep_from_op(), - defaultdict(int, {}), # Identity has no resources - ), - ( - re.ResourceControlledPhaseShift(1.23, wires=[0, 1]).resource_rep_from_op(), - defaultdict( - int, - { - re.CompressedResourceOp(re.ResourceT, {}): 51, - re.CompressedResourceOp(re.ResourceCNOT, {}): 2, - }, - ), - ), - ( - re.ResourceQFT(wires=[1, 2, 3, 4]).resource_rep_from_op(), - defaultdict( - int, - { - re.CompressedResourceOp(re.ResourceT, {}): 306, - re.CompressedResourceOp(re.ResourceCNOT, {}): 18, - re.CompressedResourceOp(re.ResourceHadamard, {}): 4, - }, - ), - ), - ) - - @pytest.mark.parametrize("op_in_gate_set", [True, False]) - @pytest.mark.parametrize("scalar", [1, 2, 5]) - @pytest.mark.parametrize("compressed_rep, expected_counts", compressed_rep_counts) - def test_counts_from_compressed_res( - self, scalar, compressed_rep, expected_counts, op_in_gate_set - ): - """Test that we can obtain counts from a compressed resource op""" - - if op_in_gate_set: - # Test that we add op directly to counts if its in the gate_set - custom_gate_set = {compressed_rep._name} - - base_gate_counts = defaultdict(int) - _counts_from_compressed_res_op( - compressed_rep, - gate_counts_dict=base_gate_counts, - gate_set=custom_gate_set, - scalar=scalar, - ) - - assert base_gate_counts == defaultdict(int, {compressed_rep: scalar}) - - else: - expected_counts = copy(expected_counts) - for resource_op, counts in expected_counts.items(): # scale expected counts - expected_counts[resource_op] = scalar * counts - - base_gate_counts = defaultdict(int) - _counts_from_compressed_res_op( - compressed_rep, - gate_counts_dict=base_gate_counts, - gate_set=DefaultGateSet, - scalar=scalar, - ) - - assert base_gate_counts == expected_counts - - @pytest.mark.parametrize( - "custom_config, num_T_gates", - ( - ( - { - "error_rz": 10e-2, - }, - 13, - ), - ( - { - "error_rz": 10e-3, - }, - 17, - ), - ( - { - "error_rz": 10e-4, - }, - 21, - ), - ), - ) - def test_counts_from_compressed_res_custom_config(self, custom_config, num_T_gates): - """Test that the function works with custom configs and a non-empty gate_counts_dict""" - base_gate_counts = defaultdict( - int, {re.ResourceT.resource_rep(): 3, re.ResourceS.resource_rep(): 5} - ) - - _counts_from_compressed_res_op( - re.ResourceRZ.resource_rep(), - base_gate_counts, - gate_set=DefaultGateSet, - config=custom_config, - ) - expected_counts = defaultdict( - int, {re.ResourceT.resource_rep(): 3 + num_T_gates, re.ResourceS.resource_rep(): 5} - ) - - assert base_gate_counts == expected_counts - - def test_clean_gate_counts(self): - """Test that the function groups operations by name instead of compressed representation.""" - - gate_counts = defaultdict( - int, - { - re.ResourceQFT.resource_rep(5): 1, - re.ResourceHadamard.resource_rep(): 3, - re.ResourceCNOT.resource_rep(): 1, - re.ResourceQFT.resource_rep(3): 4, - }, - ) - - expected_clean_counts = defaultdict( - int, {"CNOT": 1, "Hadamard": 3, "QFT(5)": 1, "QFT(3)": 4} - ) - - assert _clean_gate_counts(gate_counts) == expected_clean_counts - - @pytest.mark.parametrize( - "op, expected_resources", - ( - (re.ResourceHadamard(wires=0), re.Resources(1, 1, defaultdict(int, {"Hadamard": 1}))), - (re.ResourceRX(1.23, wires=1), re.Resources(1, 17, defaultdict(int, {"T": 17}))), - ( - re.ResourceQFT(wires=range(5)), - re.Resources(5, 541, defaultdict(int, {"Hadamard": 5, "CNOT": 26, "T": 510})), - ), - ), - ) - def test_resources_from_operation(self, op, expected_resources): - """Test that we can extract the resources from an Operation.""" - computed_resources = resources_from_operation( - op - ) # add tests that don't use default gate_set and config - assert computed_resources == expected_resources - - @staticmethod - def my_qfunc(): - """Dummy qfunc used to test resources_from_qfunc function.""" - for w in range(2): - re.ResourceHadamard(w) - - re.ResourceCNOT([0, 1]) - re.ResourceRX(1.23, 0) - re.ResourceRY(-4.56, 1) - - re.ResourceQFT(wires=[0, 1, 2]) - return qml.expval(re.ResourceHadamard(2)) - - def test_resources_from_qfunc(self): - """Test the we can extract the resources from a quantum function.""" - expected_resources_standard = re.Resources( - num_wires=3, - num_gates=24, - gate_types=defaultdict( - int, {"Hadamard": 5, "CNOT": 7, "SWAP": 1, "RX": 1, "RY": 1, "RZ": 9} - ), - ) - - computed_resources = resources_from_qfunc(self.my_qfunc, gate_set=_StandardGateSet)() - assert computed_resources == expected_resources_standard - - expected_resources_custom = re.Resources( - num_wires=3, - num_gates=190, - gate_types=defaultdict(int, {"Hadamard": 5, "CNOT": 10, "T": 175}), - ) - - my_resource_config = copy(resource_config) - my_resource_config["error_rx"] = 10e-1 - my_resource_config["error_ry"] = 10e-2 - computed_resources = resources_from_qfunc( - self.my_qfunc, gate_set=DefaultGateSet, config=my_resource_config - )() - - assert computed_resources == expected_resources_custom - - my_tape = qml.tape.QuantumScript( - ops=[ - re.ResourceHadamard(0), - re.ResourceHadamard(1), - re.ResourceCNOT([0, 1]), - re.ResourceRX(1.23, 0), - re.ResourceRY(-4.56, 1), - re.ResourceQFT(wires=[0, 1, 2]), - ], - measurements=[qml.expval(re.ResourceHadamard(2))], - ) - - def test_resources_from_tape(self): - """Test that we can extract the resources from a quantum tape""" - expected_resources_standard = re.Resources( - num_wires=3, - num_gates=24, - gate_types=defaultdict( - int, {"Hadamard": 5, "CNOT": 7, "SWAP": 1, "RX": 1, "RY": 1, "RZ": 9} - ), - ) - - computed_resources = resources_from_tape(self.my_tape, gate_set=_StandardGateSet) - assert computed_resources == expected_resources_standard - - expected_resources_custom = re.Resources( - num_wires=3, - num_gates=190, - gate_types=defaultdict(int, {"Hadamard": 5, "CNOT": 10, "T": 175}), - ) - - my_resource_config = copy(resource_config) - my_resource_config["error_rx"] = 10e-1 - my_resource_config["error_ry"] = 10e-2 - computed_resources = resources_from_tape( - self.my_tape, gate_set=DefaultGateSet, config=my_resource_config - ) - - assert computed_resources == expected_resources_custom - - def test_get_resources(self): - """Test that we can dispatch between each of the implementations above""" - op = re.ResourceControlledPhaseShift(1.23, wires=[0, 1]) - tape = qml.tape.QuantumScript(ops=[op], measurements=[qml.expval(re.ResourceHadamard(0))]) - - def circuit(): - re.ResourceControlledPhaseShift(1.23, wires=[0, 1]) - return qml.expval(re.ResourceHadamard(0)) - - res_from_op = get_resources(op) - res_from_tape = get_resources(tape) - res_from_circuit = get_resources(circuit)() - - expected_resources = re.Resources( - num_wires=2, num_gates=53, gate_types=defaultdict(int, {"CNOT": 2, "T": 51}) - ) - - assert res_from_op == expected_resources - assert res_from_tape == expected_resources - assert res_from_circuit == expected_resources From 95232dfce56d42b91c0abffc1d0def6515cb67d2 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Wed, 7 May 2025 15:58:18 -0400 Subject: [PATCH 3/4] remove source code --- .../labs/resource_estimation/__init__.py | 201 -- .../labs/resource_estimation/ops/__init__.py | 70 - .../labs/resource_estimation/ops/identity.py | 229 --- .../ops/op_math/__init__.py | 17 - .../ops/op_math/controlled_ops.py | 1812 ----------------- .../ops/op_math/symbolic.py | 1251 ------------ .../resource_estimation/ops/qubit/__init__.py | 19 - .../ops/qubit/non_parametric_ops.py | 1024 ---------- .../ops/qubit/parametric_ops_multi_qubit.py | 1223 ----------- .../ops/qubit/parametric_ops_single_qubit.py | 795 -------- .../ops/qubit/qchem_ops.py | 689 ------- .../resource_estimation/resource_container.py | 347 ---- .../resource_estimation/resource_operator.py | 187 -- .../resource_estimation/resource_tracking.py | 319 --- .../resource_estimation/templates/__init__.py | 40 - .../templates/stateprep.py | 393 ---- .../templates/subroutines.py | 1635 --------------- .../resource_estimation/templates/trotter.py | 494 ----- 18 files changed, 10745 deletions(-) delete mode 100644 pennylane/labs/resource_estimation/ops/__init__.py delete mode 100644 pennylane/labs/resource_estimation/ops/identity.py delete mode 100644 pennylane/labs/resource_estimation/ops/op_math/__init__.py delete mode 100644 pennylane/labs/resource_estimation/ops/op_math/controlled_ops.py delete mode 100644 pennylane/labs/resource_estimation/ops/op_math/symbolic.py delete mode 100644 pennylane/labs/resource_estimation/ops/qubit/__init__.py delete mode 100644 pennylane/labs/resource_estimation/ops/qubit/non_parametric_ops.py delete mode 100644 pennylane/labs/resource_estimation/ops/qubit/parametric_ops_multi_qubit.py delete mode 100644 pennylane/labs/resource_estimation/ops/qubit/parametric_ops_single_qubit.py delete mode 100644 pennylane/labs/resource_estimation/ops/qubit/qchem_ops.py delete mode 100644 pennylane/labs/resource_estimation/resource_container.py delete mode 100644 pennylane/labs/resource_estimation/resource_operator.py delete mode 100644 pennylane/labs/resource_estimation/resource_tracking.py delete mode 100644 pennylane/labs/resource_estimation/templates/__init__.py delete mode 100644 pennylane/labs/resource_estimation/templates/stateprep.py delete mode 100644 pennylane/labs/resource_estimation/templates/subroutines.py delete mode 100644 pennylane/labs/resource_estimation/templates/trotter.py diff --git a/pennylane/labs/resource_estimation/__init__.py b/pennylane/labs/resource_estimation/__init__.py index 7ae769f66e3..7d70e810181 100644 --- a/pennylane/labs/resource_estimation/__init__.py +++ b/pennylane/labs/resource_estimation/__init__.py @@ -28,209 +28,8 @@ .. autosummary:: :toctree: api - ~Resources ~ResourceOperator -Operators -~~~~~~~~~ - -.. autosummary:: - :toctree: api - - ~ResourceCCZ - ~ResourceCH - ~ResourceCNOT - ~ResourceControlledPhaseShift - ~ResourceCRot - ~ResourceCRX - ~ResourceCRY - ~ResourceCRZ - ~ResourceCSWAP - ~ResourceCY - ~ResourceCZ - ~ResourceDoubleExcitation - ~ResourceDoubleExcitationMinus - ~ResourceDoubleExcitationPlus - ~ResourceFermionicSWAP - ~ResourceGlobalPhase - ~ResourceHadamard - ~ResourceIdentity - ~ResourceIsingXX - ~ResourceIsingXY - ~ResourceIsingYY - ~ResourceIsingZZ - ~ResourceMultiControlledX - ~ResourceMultiRZ - ~ResourceOrbitalRotation - ~ResourcePauliRot - ~ResourcePhaseShift - ~ResourcePSWAP - ~ResourceRot - ~ResourceRX - ~ResourceRY - ~ResourceRZ - ~ResourceS - ~ResourceSingleExcitation - ~ResourceSingleExcitationMinus - ~ResourceSingleExcitationPlus - ~ResourceSWAP - ~ResourceT - ~ResourceToffoli - ~ResourceX - ~ResourceY - ~ResourceZ - -Symbolic Operators -~~~~~~~~~~~~~~~~~~ - -.. autosummary:: - :toctree: api - - ~ResourceAdjoint - ~ResourceControlled - ~ResourceExp - ~ResourcePow - ~ResourceProd - -Templates -~~~~~~~~~ - -.. autosummary:: - :toctree: api - - ~ResourceAmplitudeAmplification - ~ResourceBasisRotation - ~ResourcePrepSelPrep - ~ResourceQFT - ~ResourceQPE - ~ResourceQuantumPhaseEstimation - ~ResourceQubitization - ~ResourceQROM - ~ResourceReflection - ~ResourceSelect - ~ResourceTrotterProduct - ~ResourceTrotterizedQfunc - ~resource_trotterize - ~ResourceControlledSequence - ~ResourceModExp - ~ResourceMultiplier - ~ResourcePhaseAdder - -State Preparation Templates -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autosummary:: - :toctree: api - - ~ResourceBasisState - ~ResourceStatePrep - ~ResourceSuperposition - ~ResourceMottonenStatePreparation - -Tracking Resources -~~~~~~~~~~~~~~~~~~ - -.. autosummary:: - :toctree: api - - ~get_resources - -Resource Object Functions: -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autosummary:: - :toctree: api - - ~add_in_series - ~add_in_parallel - ~mul_in_series - ~mul_in_parallel - ~substitute """ from .resource_operator import ResourceOperator, ResourcesNotDefined -from .resource_tracking import DefaultGateSet, get_resources, resource_config - -from .resource_container import ( - CompressedResourceOp, - Resources, - add_in_series, - add_in_parallel, - mul_in_series, - mul_in_parallel, - substitute, -) - -from .ops import ( - ResourceAdjoint, - ResourceCCZ, - ResourceCH, - ResourceCNOT, - ResourceControlled, - ResourceControlledPhaseShift, - ResourceCRot, - ResourceCRX, - ResourceCRY, - ResourceCRZ, - ResourceCSWAP, - ResourceCY, - ResourceCZ, - ResourceDoubleExcitation, - ResourceDoubleExcitationMinus, - ResourceDoubleExcitationPlus, - ResourceExp, - ResourceFermionicSWAP, - ResourceGlobalPhase, - ResourceHadamard, - ResourceIdentity, - ResourceIsingXX, - ResourceIsingXY, - ResourceIsingYY, - ResourceIsingZZ, - ResourceMultiControlledX, - ResourceMultiRZ, - ResourceOrbitalRotation, - ResourcePauliRot, - ResourcePow, - ResourcePSWAP, - ResourcePhaseShift, - ResourceProd, - ResourceRot, - ResourceRX, - ResourceRY, - ResourceRZ, - ResourceS, - ResourceSingleExcitation, - ResourceSingleExcitationMinus, - ResourceSingleExcitationPlus, - ResourceSWAP, - ResourceT, - ResourceToffoli, - ResourceX, - ResourceY, - ResourceZ, -) - -from .templates import ( - ResourceControlledSequence, - ResourceModExp, - ResourceMultiplier, - ResourcePhaseAdder, - ResourceBasisRotation, - ResourcePrepSelPrep, - ResourceQFT, - ResourceQPE, - ResourceQuantumPhaseEstimation, - ResourceQubitization, - ResourceQROM, - ResourceReflection, - ResourceSelect, - ResourceStatePrep, - ResourceTrotterProduct, - ResourceTrotterizedQfunc, - resource_trotterize, - ResourceMottonenStatePreparation, - ResourceSuperposition, - ResourceAmplitudeAmplification, - ResourceBasisState, -) diff --git a/pennylane/labs/resource_estimation/ops/__init__.py b/pennylane/labs/resource_estimation/ops/__init__.py deleted file mode 100644 index f0aa872a9a6..00000000000 --- a/pennylane/labs/resource_estimation/ops/__init__.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2025 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. -r"""This module contains resource operators for PennyLane Operators""" - -from .identity import ( - ResourceGlobalPhase, - ResourceIdentity, -) - -from .qubit import ( - ResourceDoubleExcitation, - ResourceDoubleExcitationMinus, - ResourceDoubleExcitationPlus, - ResourceFermionicSWAP, - ResourceHadamard, - ResourceIsingXX, - ResourceIsingXY, - ResourceIsingYY, - ResourceIsingZZ, - ResourceMultiRZ, - ResourceOrbitalRotation, - ResourcePauliRot, - ResourcePhaseShift, - ResourcePSWAP, - ResourceRot, - ResourceRX, - ResourceRY, - ResourceRZ, - ResourceS, - ResourceSingleExcitation, - ResourceSingleExcitationMinus, - ResourceSingleExcitationPlus, - ResourceSWAP, - ResourceT, - ResourceX, - ResourceY, - ResourceZ, -) - -from .op_math import ( - ResourceAdjoint, - ResourceCY, - ResourceCH, - ResourceCZ, - ResourceCSWAP, - ResourceCCZ, - ResourceCRot, - ResourceCRX, - ResourceCRY, - ResourceCRZ, - ResourceExp, - ResourceToffoli, - ResourceMultiControlledX, - ResourceCNOT, - ResourceControlled, - ResourceControlledPhaseShift, - ResourcePow, - ResourceProd, -) diff --git a/pennylane/labs/resource_estimation/ops/identity.py b/pennylane/labs/resource_estimation/ops/identity.py deleted file mode 100644 index 76ea1ab9e16..00000000000 --- a/pennylane/labs/resource_estimation/ops/identity.py +++ /dev/null @@ -1,229 +0,0 @@ -# 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. -r"""Resource operators for identity operations.""" -from typing import Dict - -import pennylane as qml -import pennylane.labs.resource_estimation as re - -# pylint: disable=arguments-differ,no-self-use,too-many-ancestors - - -class ResourceIdentity(qml.Identity, re.ResourceOperator): - r"""Resource class for the Identity gate. - - Args: - wires (Iterable[Any] or Any): Wire label(s) that the identity acts on. - id (str): custom label given to an operator instance, - can be useful for some applications where the instance has to be identified. - - Resources: - The Identity gate is treated as a free gate and thus it cannot be decomposed - further. Requesting the resources of this gate returns an empty dictionary. - - .. seealso:: :class:`~.Identity` - - """ - - @staticmethod - def _resource_decomp(*args, **kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The Identity gate is treated as a free gate and thus it cannot be decomposed - further. Requesting the resources of this gate returns an empty dictionary. - - Returns: - dict: empty dictionary - """ - return {} - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls, **kwargs) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation is also an empty dictionary. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated values are the counts. - """ - return {cls.resource_rep(): 1} - - @classmethod - def controlled_resource_decomp( - cls, num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): The number of control qubits, that are triggered when in the :math:`|0\rangle` state. - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The Identity gate acts trivially when controlled. The resources of this operation are - the original (un-controlled) operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated values are the counts. - """ - return {cls.resource_rep(): 1} - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - The Identity gate acts trivially when raised to a power. The resources of this - operation are the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated values are the counts. - """ - return {cls.resource_rep(): 1} - - -class ResourceGlobalPhase(qml.GlobalPhase, re.ResourceOperator): - r"""Resource class for the GlobalPhase gate. - - Args: - phi (TensorLike): the global phase - wires (Iterable[Any] or Any): unused argument - the operator is applied to all wires - id (str): custom label given to an operator instance, - can be useful for some applications where the instance has to be identified. - - Resources: - The GlobalPhase gate is treated as a free gate and thus it cannot be decomposed - further. Requesting the resources of this gate returns an empty dictionary. - - .. seealso:: :class:`~.GlobalPhase` - - """ - - @staticmethod - def _resource_decomp(*args, **kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The GlobalPhase gate is treated as a free gate and thus it cannot be decomposed - further. Requesting the resources of this gate returns an empty dictionary. - - Returns: - dict: empty dictionary - """ - return {} - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls, **kwargs) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @staticmethod - def adjoint_resource_decomp() -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of a global phase operator changes the sign of the phase, thus - the resources of the adjoint operation is the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated values are the counts. - """ - return {re.ResourceGlobalPhase.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are generated from the fact that a global phase controlled on a - single qubit is equivalent to a local phase shift on that control qubit. - - This idea can be generalized to a multi-qubit global phase by introducing one - 'clean' auxilliary qubit which gets reset at the end of the computation. In this - case, we sandwich the phase shift operation with two multi-controlled X gates. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated values are the counts. - """ - if num_ctrl_wires == 1: - gate_types = {re.ResourcePhaseShift.resource_rep(): 1} - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 - - return gate_types - - ps = re.ResourcePhaseShift.resource_rep() - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - return {ps: 1, mcx: 2} - - @staticmethod - def pow_resource_decomp(z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a global phase produces a sum of global phases. - The resources simplify to just one total global phase operator. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated values are the counts. - """ - return {re.ResourceGlobalPhase.resource_rep(): 1} diff --git a/pennylane/labs/resource_estimation/ops/op_math/__init__.py b/pennylane/labs/resource_estimation/ops/op_math/__init__.py deleted file mode 100644 index 1bfa7b8dc0f..00000000000 --- a/pennylane/labs/resource_estimation/ops/op_math/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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. -r"""This module contains experimental resource estimation functionality.""" - -from .controlled_ops import * -from .symbolic import * diff --git a/pennylane/labs/resource_estimation/ops/op_math/controlled_ops.py b/pennylane/labs/resource_estimation/ops/op_math/controlled_ops.py deleted file mode 100644 index bf722bf5f50..00000000000 --- a/pennylane/labs/resource_estimation/ops/op_math/controlled_ops.py +++ /dev/null @@ -1,1812 +0,0 @@ -# Copyright 2025 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. -r"""Resource operators for controlled operations.""" -from collections import defaultdict -from typing import Dict - -import pennylane as qml -import pennylane.labs.resource_estimation as re - -# pylint: disable=arguments-differ,too-many-ancestors,too-many-arguments,too-many-positional-arguments - - -class ResourceCH(qml.CH, re.ResourceOperator): - r"""Resource class for the CH gate. - - Args: - wires (Sequence[int]): the wires the operation acts on - - Resources: - The resources are derived from the following identities (as presented in this - `blog post `_): - - .. math:: - - \begin{align} - \hat{H} &= \hat{R}_{y}(\frac{\pi}{4}) \cdot \hat{Z} \cdot \hat{R}_{y}(\frac{-\pi}{4}), \\ - \hat{Z} &= \hat{H} \cdot \hat{X} \cdot \hat{H}. - \end{align} - - Specifically, the resources are defined as two :class:`~.ResourceRY`, two - :class:`~.ResourceHadamard` and one :class:`~.ResourceCNOT` gates. - - .. seealso:: :class:`~.CH` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceCH.resources() - {Hadamard: 2, RY: 2, CNOT: 1} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are derived from the following identities (as presented in this - `blog post `_): - - .. math:: - - \begin{align} - \hat{H} &= \hat{R}_{y}(\frac{\pi}{4}) \cdot \hat{Z} \cdot \hat{R}_{y}(\frac{-\pi}{4}), \\ - \hat{Z} &= \hat{H} \cdot \hat{X} \cdot \hat{H}. - \end{align} - - Specifically, the resources are defined as two :class:`~.ResourceRY`, two - :class:`~.ResourceHadamard` and one :class:`~.ResourceCNOT` gates. - """ - gate_types = {} - - ry = re.ResourceRY.resource_rep() - h = re.ResourceHadamard.resource_rep() - cnot = re.ResourceCNOT.resource_rep() - - gate_types[h] = 2 - gate_types[ry] = 2 - gate_types[cnot] = 1 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed using the symbolic :class:`~.ResourceControlled`. The resources - are computed according to the :code:`controlled_resource_decomp()` of the base - :class:`~.ResourceHadamard` class. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return { - re.ResourceControlled.resource_rep( - re.ResourceHadamard, {}, num_ctrl_wires + 1, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - This operation is self-inverse, thus when raised to even integer powers acts like - the identity operator and raised to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {re.ResourceIdentity.resource_rep(): 1} if z % 2 == 0 else {cls.resource_rep(): 1} - - -class ResourceCY(qml.CY, re.ResourceOperator): - r"""Resource class for the CY gate. - - Args: - wires (Sequence[int]): the wires the operation acts on - id (str): custom label given to an operator instance, - can be useful for some applications where the instance has to be identified. - - Resources: - The resources are derived from the following identity: - - .. math:: \hat{Y} = \hat{S} \cdot \hat{X} \cdot \hat{S}^{\dagger}. - - By replacing the :class:`~.ResourceX` gate with a :class:`~.ResourceCNOT` we - obtain the controlled decomposition. Specifically, the resources are defined as a - :class:`~.ResourceCNOT` gate conjugated by a pair of :class:`~.ResourceS` gates. - - .. seealso:: :class:`~.CY` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceCY.resources() - {CNOT: 1, S: 1, Adjoint(S): 1} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are derived from the following identity: - - .. math:: \hat{Y} = \hat{S} \cdot \hat{X} \cdot \hat{S}^{\dagger}. - - By replacing the :class:`~.ResourceX` gate with a :class:`~.ResourceCNOT` we - obtain the controlled decomposition. Specifically, the resources are defined as a - :class:`~.ResourceCNOT` gate conjugated by a pair of :class:`~.ResourceS` gates. - """ - gate_types = {} - - cnot = re.ResourceCNOT.resource_rep() - s = re.ResourceS.resource_rep() - s_dag = re.ResourceAdjoint.resource_rep(re.ResourceS, {}) - - gate_types[cnot] = 1 - gate_types[s] = 1 - gate_types[s_dag] = 1 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed using the symbolic :class:`~.ResourceControlled`. The resources - are computed according to the :code:`controlled_resource_decomp()` of the base - :class:`~.ResourceY` class. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return { - re.ResourceControlled.resource_rep( - re.ResourceY, {}, num_ctrl_wires + 1, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - This operation is self-inverse, thus when raised to even integer powers acts like - the identity operator and raised to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {re.ResourceIdentity.resource_rep(): 1} if z % 2 == 0 else {cls.resource_rep(): 1} - - -class ResourceCZ(qml.CZ, re.ResourceOperator): - r"""Resource class for the CZ gate. - - Args: - wires (Sequence[int]): the wires the operation acts on - - Resources: - The resources are derived from the following identity: - - .. math:: \hat{Z} = \hat{H} \cdot \hat{X} \cdot \hat{H}. - - By replacing the :class:`~.ResourceX` gate with a :class:`~.ResourceCNOT` we obtain - the controlled decomposition. Specifically, the resources are defined as a - :class:`~.ResourceCNOT` gate conjugated by a pair of :class:`~.ResourceHadamard` gates. - - .. seealso:: :class:`~.CZ` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceCZ.resources() - {CNOT: 1, Hadamard: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are derived from the following identity: - - .. math:: \hat{Z} = \hat{H} \cdot \hat{X} \cdot \hat{H}. - - By replacing the :class:`~.ResourceX` gate with a :class:`~.ResourceCNOT` we obtain - the controlled decomposition. Specifically, the resources are defined as a - :class:`~.ResourceCNOT` gate conjugated by a pair of :class:`~.ResourceHadamard` gates. - """ - gate_types = {} - - cnot = re.ResourceCNOT.resource_rep() - h = re.ResourceHadamard.resource_rep() - - gate_types[cnot] = 1 - gate_types[h] = 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed using the symbolic :class:`~.ResourceControlled`. The resources - are computed according to the :code:`controlled_resource_decomp()` of the base - :class:`~.ResourceZ` class. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1 and num_ctrl_values == 0 and num_work_wires == 0: - return {re.ResourceCCZ.resource_rep(): 1} - - return { - re.ResourceControlled.resource_rep( - re.ResourceZ, {}, num_ctrl_wires + 1, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - This operation is self-inverse, thus when raised to even integer powers acts like - the identity operator and raised to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {re.ResourceIdentity.resource_rep(): 1} if z % 2 == 0 else {cls.resource_rep(): 1} - - -class ResourceCSWAP(qml.CSWAP, re.ResourceOperator): - r"""Resource class for the CSWAP gate. - - Resources: - The resources are taken from Figure 1d of `arXiv:2305.18128 `_. - - The circuit which applies the SWAP operation on wires (1, 2) and controlled on wire (0) is - defined as: - - .. code-block:: bash - - 0: ────╭●────┤ - 1: ─╭X─├●─╭X─┤ - 2: ─╰●─╰X─╰●─┤ - - .. seealso:: :class:`~.CSWAP` - - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are taken from Figure 1d of `arXiv:2305.18128 `_. - - The circuit which applies the SWAP operation on wires (1, 2) and controlled on wire (0) is - defined as: - - .. code-block:: bash - - 0: ────╭●────┤ - 1: ─╭X─├●─╭X─┤ - 2: ─╰●─╰X─╰●─┤ - """ - gate_types = {} - - tof = re.ResourceToffoli.resource_rep() - cnot = re.ResourceCNOT.resource_rep() - - gate_types[tof] = 1 - gate_types[cnot] = 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed using the symbolic :class:`~.ResourceControlled`. The resources - are computed according to the :code:`controlled_resource_decomp()` of the base - :class:`~.ResourceSWAP` class. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return { - re.ResourceControlled.resource_rep( - re.ResourceSWAP, {}, num_ctrl_wires + 1, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - This operation is self-inverse, thus when raised to even integer powers acts like - the identity operator and raised to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {re.ResourceIdentity.resource_rep(): 1} if z % 2 == 0 else {cls.resource_rep(): 1} - - -class ResourceCCZ(qml.CCZ, re.ResourceOperator): - r"""Resource class for the CCZ gate. - - Args: - wires (Sequence[int]): the subsystem the gate acts on - - Resources: - The resources are derived from the following identity: - - .. math:: \hat{Z} = \hat{H} \cdot \hat{X} \cdot \hat{H}. - - By replacing the :class:`~.ResourceX` gate with a :class:`~.ResourceToffoli` we obtain - the controlled decomposition. Specifically, the resources are defined as a - :class:`~.ResourceToffoli` gate conjugated by a pair of :class:`~.ResourceHadamard` gates. - - .. seealso:: :class:`~.CCZ` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceCCZ.resources() - {Toffoli: 1, Hadamard: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are derived from the following identity: - - .. math:: \hat{Z} = \hat{H} \cdot \hat{X} \cdot \hat{H}. - - By replacing the :class:`~.ResourceX` gate with a :class:`~.ResourceToffoli` we obtain - the controlled decomposition. Specifically, the resources are defined as a - :class:`~.ResourceToffoli` gate conjugated by a pair of :class:`~.ResourceHadamard` gates. - """ - gate_types = {} - - toffoli = re.ResourceToffoli.resource_rep() - h = re.ResourceHadamard.resource_rep() - - gate_types[toffoli] = 1 - gate_types[h] = 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls, **kwargs): - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed using the symbolic :class:`~.ResourceControlled`. The resources - are computed according to the :code:`controlled_resource_decomp()` of the base - :class:`~.ResourceZ` class. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return { - re.ResourceControlled.resource_rep( - re.ResourceZ, {}, num_ctrl_wires + 2, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - This operation is self-inverse, thus when raised to even integer powers acts like - the identity operator and raised to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {re.ResourceIdentity.resource_rep(): 1} if z % 2 == 0 else {cls.resource_rep(): 1} - - -class ResourceCNOT(qml.CNOT, re.ResourceOperator): - r"""Resource class for the CNOT gate. - - Args: - wires (Sequence[int]): the wires the operation acts on - - Resources: - The CNOT gate is treated as a fundamental gate and thus it cannot be decomposed - further. Requesting the resources of this gate raises a :code:`ResourcesNotDefined` error. - - .. seealso:: :class:`~.CNOT` - - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The CNOT gate is treated as a fundamental gate and thus it cannot be decomposed - further. Requesting the resources of this gate raises a :code:`ResourcesNotDefined` error. - - Raises: - ResourcesNotDefined: This gate is fundamental, no further decomposition defined. - """ - raise re.ResourcesNotDefined - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @classmethod - def controlled_resource_decomp( - cls, num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed as one general :class:`~.ResourceMultiControlledX` gate. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1 and num_ctrl_values == 0 and num_work_wires == 0: - return {re.ResourceToffoli.resource_rep(): 1} - - return { - re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires + 1, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - This operation is self-inverse, thus when raised to even integer powers acts like - the identity operator and raised to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {re.ResourceIdentity.resource_rep(): 1} if z % 2 == 0 else {cls.resource_rep(): 1} - - -class ResourceToffoli(qml.Toffoli, re.ResourceOperator): - r"""Resource class for the Toffoli gate. - - Args: - wires (Sequence[int]): the subsystem the gate acts on - - Resources: - The resources are obtained from Figure 1 of `Jones 2012 `_. - - The circuit which applies the Toffoli gate on target wire 'target' with control wires - ('c1', 'c2') is defined as: - - .. code-block:: bash - - c1: ─╭●────╭X──T†────────╭X────╭●───────────────╭●─┤ - c2: ─│──╭X─│──╭●───T†─╭●─│──╭X─│────────────────╰Z─┤ - aux1: ─╰X─│──│──╰X───T──╰X─│──│──╰X────────────────║─┤ - aux2: ──H─╰●─╰●──T─────────╰●─╰●──H──S─╭●──H──┤↗├──║─┤ - target: ─────────────────────────────────╰X──────║───║─┤ - ╚═══╝ - - Specifically, the resources are defined as nine :class:`~.ResourceCNOT` gates, three - :class:`~.ResourceHadamard` gates, one :class:`~.ResourceCZ` gate, one :class:`~.ResourceS` - gate, two :class:`~.ResourceT` gates and two adjoint :class:`~.ResourceT` gates. - - .. seealso:: :class:`~.Toffoli` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceToffoli.resources() - {CNOT: 9, Hadamard: 3, S: 1, CZ: 1, T: 2, Adjoint(T): 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are obtained from Figure 1 of `Jones 2012 `_. - - The circuit which applies the Toffoli gate on target wire 'target' with control wires - ('c1', 'c2') is defined as: - - .. code-block:: bash - - c1: ─╭●────╭X──T†────────╭X────╭●───────────────╭●─┤ - c2: ─│──╭X─│──╭●───T†─╭●─│──╭X─│────────────────╰Z─┤ - aux1: ─╰X─│──│──╰X───T──╰X─│──│──╰X────────────────║─┤ - aux2: ──H─╰●─╰●──T─────────╰●─╰●──H──S─╭●──H──┤↗├──║─┤ - target: ─────────────────────────────────╰X──────║───║─┤ - ╚═══╝ - - Specifically, the resources are defined as nine :class:`~.ResourceCNOT` gates, three - :class:`~.ResourceHadamard` gates, one :class:`~.ResourceCZ` gate, one :class:`~.ResourceS` - gate, two :class:`~.ResourceT` gates and two adjoint :class:`~.ResourceT` gates. - """ - gate_types = {} - - cnot = re.ResourceCNOT.resource_rep() - t = re.ResourceT.resource_rep() - h = re.ResourceHadamard.resource_rep() - s = re.ResourceS.resource_rep() - cz = re.ResourceCZ.resource_rep() - t_dag = re.ResourceAdjoint.resource_rep(re.ResourceT, {}) - - gate_types[cnot] = 9 - gate_types[h] = 3 - gate_types[s] = 1 - gate_types[cz] = 1 - gate_types[t] = 2 - gate_types[t_dag] = 2 - - return gate_types - - @staticmethod - def textbook_resource_decomp() -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are taken from Figure 4.9 of `Nielsen, M. A., & Chuang, I. L. (2010) `_. - - The circuit is defined as: - - .. code-block:: bash - - 0: ───────────╭●───────────╭●────╭●──T──╭●─┤ - 1: ────╭●─────│─────╭●─────│───T─╰X──T†─╰X─┤ - 2: ──H─╰X──T†─╰X──T─╰X──T†─╰X──T──H────────┤ - - Specifically, the resources are defined as six :class:`~.ResourceCNOT` gates, two - :class:`~.ResourceHadamard` gates, four :class:`~.ResourceT` gates and three adjoint - :class:`~.ResourceT` gates. - """ - gate_types = {} - - cnot = re.ResourceCNOT.resource_rep() - t = re.ResourceT.resource_rep() - h = re.ResourceHadamard.resource_rep() - t_dag = re.ResourceAdjoint.resource_rep(re.ResourceT, {}) - - gate_types[cnot] = 6 - gate_types[h] = 2 - gate_types[t] = 4 - gate_types[t_dag] = 3 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed as one general :class:`~.ResourceMultiControlledX` gate. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return { - re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires + 2, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - This operation is self-inverse, thus when raised to even integer powers acts like - the identity operator and raised to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {re.ResourceIdentity.resource_rep(): 1} if z % 2 == 0 else {cls.resource_rep(): 1} - - -class ResourceMultiControlledX(qml.MultiControlledX, re.ResourceOperator): - r"""Resource class for the MultiControlledX gate. - - Args: - wires (Union[Wires, Sequence[int], or int]): control wire(s) followed by a single target wire (the last entry of ``wires``) where - the operation acts on - control_values (Union[bool, list[bool], int, list[int]]): The value(s) the control wire(s) - should take. Integers other than 0 or 1 will be treated as ``int(bool(x))``. - work_wires (Union[Wires, Sequence[int], or int]): optional work wires used to decompose - the operation into a series of :class:`~.Toffoli` gates - - Resource Parameters: - * num_ctrl_wires (int): the number of qubits the operation is controlled on - * num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - * num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are obtained from Table 3 of `Claudon, B., Zylberman, J., Feniou, C. et al. - `_. Specifically, the - resources are defined as the following rules: - - * If there is only one control qubit, treat the resources as a :class:`~.ResourceCNOT` gate. - - * If there are two control qubits, treat the resources as a :class:`~.ResourceToffoli` gate. - - * If there are three control qubits, the resources are two :class:`~.ResourceCNOT` gates and one :class:`~.ResourceToffoli` gate. - - * If there are more than three control qubits (:math:`n`), the resources are defined as :math:`36n - 111` :class:`~.ResourceCNOT` gates. - - .. seealso:: :class:`~.MultiControlledX` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceMultiControlledX.resources(num_ctrl_wires=5, num_ctrl_values=2, num_work_wires=3) - {X: 4, CNOT: 69} - """ - - @staticmethod - def _resource_decomp( - num_ctrl_wires, - num_ctrl_values, - num_work_wires, - **kwargs, # pylint: disable=unused-argument - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are obtained from Table 3 of `Claudon, B., Zylberman, J., Feniou, C. et al. - `_. Specifically, the - resources are defined as the following rules: - - * If there are no control qubits, treat the operation as a :class:`~.ResourceX` gate. - - * If there is only one control qubit, treat the resources as a :class:`~.ResourceCNOT` gate. - - * If there are two control qubits, treat the resources as a :class:`~.ResourceToffoli` gate. - - * If there are three control qubits, the resources are two :class:`~.ResourceCNOT` gates and - one :class:`~.ResourceToffoli` gate. - - * If there are more than three control qubits (:math:`n`), the resources are defined as - :math:`36n - 111` :class:`~.ResourceCNOT` gates. - """ - gate_types = defaultdict(int) - - x = re.ResourceX.resource_rep() - if num_ctrl_values: - gate_types[x] = num_ctrl_values * 2 - - if num_ctrl_wires == 0: - gate_types[x] += 1 - return gate_types - - cnot = re.ResourceCNOT.resource_rep() - if num_ctrl_wires == 1: - gate_types[cnot] = 1 - return gate_types - - toffoli = re.ResourceToffoli.resource_rep() - if num_ctrl_wires == 2: - gate_types[toffoli] = 1 - return gate_types - - if num_ctrl_wires == 3: - gate_types[cnot] = 2 - gate_types[toffoli] = 1 - return gate_types - - gate_types[cnot] = 36 * num_ctrl_wires - 111 - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * num_ctrl_wires (int): the number of qubits the operation is controlled on - * num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - * num_work_wires (int): the number of additional qubits that can be used for decomposition - """ - num_control = len(self.hyperparameters["control_wires"]) - num_work_wires = len(self.hyperparameters["work_wires"]) - - num_control_values = len([val for val in self.hyperparameters["control_values"] if not val]) - - return { - "num_ctrl_wires": num_control, - "num_ctrl_values": num_control_values, - "num_work_wires": num_work_wires, - } - - @classmethod - def resource_rep( - cls, num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - return re.CompressedResourceOp( - cls, - { - "num_ctrl_wires": num_ctrl_wires, - "num_ctrl_values": num_ctrl_values, - "num_work_wires": num_work_wires, - }, - ) - - @classmethod - def adjoint_resource_decomp( - cls, num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(num_ctrl_wires, num_ctrl_values, num_work_wires): 1} - - @classmethod - def controlled_resource_decomp( - cls, - outer_num_ctrl_wires, - outer_num_ctrl_values, - outer_num_work_wires, - num_ctrl_wires, - num_ctrl_values, - num_work_wires, - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - outer_num_ctrl_wires (int): The number of control qubits to further control the base - controlled operation upon. - outer_num_ctrl_values (int): The subset of those control qubits, which further control - the base controlled operation, which are controlled when in the :math:`|0\rangle` state. - outer_num_work_wires (int): the number of additional qubits that can be used in the - decomposition for the further controlled, base control oepration. - num_ctrl_wires (int): the number of control qubits of the operation - num_ctrl_values (int): The subset of control qubits of the operation, that are controlled - when in the :math:`|0\rangle` state. - num_work_wires (int): The number of additional qubits that can be used for the - decomposition of the operation. - - Resources: - The resources are derived by combining the control qubits, control-values and - work qubits into a single instance of :class:`~.ResourceMultiControlledX` gate, controlled - on the whole set of control-qubits. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return cls.resources( - outer_num_ctrl_wires + num_ctrl_wires, - outer_num_ctrl_values + num_ctrl_values, - outer_num_work_wires + num_work_wires, - ) - - @classmethod - def pow_resource_decomp( - cls, z, num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - This operation is self-inverse, thus when raised to even integer powers acts like - the identity operator and raised to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return ( - {} - if z % 2 == 0 - else {cls.resource_rep(num_ctrl_wires, num_ctrl_values, num_work_wires): 1} - ) - - -class ResourceCRX(qml.CRX, re.ResourceOperator): - r"""Resource class for the CRX gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int]): the wire the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are taken from Figure 1b of `Gheorghiu, V., Mosca, M. & Mukhopadhyay - `_. In combination with the following identity: - - .. math:: \hat{RX} = \hat{H} \cdot \hat{RZ} \cdot \hat{H}, - - we can express the :code:`CRX` gate as a :code:`CRZ` gate conjugated by :code:`Hadamard` - gates. The expression for controlled-RZ gates is used as defined in the reference above. - Specifically, the resources are defined as two :class:`~.ResourceCNOT` gates, two - :class:`~.ResourceHadamard` gates and two :class:`~.ResourceRZ` gates. - - .. seealso:: :class:`~.CRX` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceCRX.resources() - {CNOT: 2, RZ: 2, Hadamard: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are taken from Figure 1b of `Gheorghiu, V., Mosca, M. & Mukhopadhyay - `_. In combination with the following identity: - - .. math:: \hat{RX} = \hat{H} \cdot \hat{RZ} \cdot \hat{H}, - - we can express the :code:`CRX` gate as a :code:`CRZ` gate conjugated by :code:`Hadamard` - gates. The expression for controlled-RZ gates is used as defined in the reference above. - Specifically, the resources are defined as two :class:`~.ResourceCNOT` gates, two - :class:`~.ResourceHadamard` gates and two :class:`~.ResourceRZ` gates. - """ - gate_types = {} - - h = re.ResourceHadamard.resource_rep() - rz = re.ResourceRZ.resource_rep() - cnot = re.ResourceCNOT.resource_rep() - - gate_types[cnot] = 2 - gate_types[rz] = 2 - gate_types[h] = 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of a single qubit rotation changes the sign of the rotation angle, - thus the resources of the adjoint operation result in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed using the symbolic :class:`~.ResourceControlled`. The resources - are computed according to the :code:`controlled_resource_decomp()` of the base - :class:`~.ResourceRX` class. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return { - re.ResourceControlled.resource_rep( - re.ResourceRX, {}, num_ctrl_wires + 1, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a single qubit rotation produces a sum of rotations. - The resources simplify to just one total single qubit rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - -class ResourceCRY(qml.CRY, re.ResourceOperator): - r"""Resource class for the CRY gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int]): the wire the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are taken from Figure 1b of `Gheorghiu, V., Mosca, M. & Mukhopadhyay - `_. In combination with the following identity: - - .. math:: \hat{RY}(\theta) = \hat{X} \cdot \hat{RY}(- \theta) \cdot \hat{X}. - - By replacing the :class:`~.ResourceX` gates with :class:`~.ResourceCNOT` gates, we obtain a - controlled-version of this identity. Thus we are able to constructively or destructively - interfere the gates based on the value of the control qubit. Specifically, the resources are - defined as two :class:`~.ResourceCNOT` gates and two :class:`~.ResourceRY` gates. - - .. seealso:: :class:`~.CRY` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceCRY.resources() - {CNOT: 2, RY: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are taken from Figure 1b of `Gheorghiu, V., Mosca, M. & Mukhopadhyay - `_. In combination with the following identity: - - .. math:: \hat{RY}(\theta) = \hat{X} \cdot \hat{RY}(- \theta) \cdot \hat{X}. - - By replacing the :code:`X` gates with :code:`CNOT` gates, we obtain a controlled-version of this - identity. Thus we are able to constructively or destructively interfere the gates based on the value - of the control qubit. Specifically, the resources are defined as two :class:`~.ResourceCNOT` gates - and two :class:`~.ResourceRY` gates. - """ - gate_types = {} - - cnot = re.ResourceCNOT.resource_rep() - ry = re.ResourceRY.resource_rep() - - gate_types[cnot] = 2 - gate_types[ry] = 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of a single qubit rotation changes the sign of the rotation angle, - thus the resources of the adjoint operation result in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed using the symbolic :class:`~.ResourceControlled`. The resources - are computed according to the :code:`controlled_resource_decomp()` of the base - :class:`~.ResourceRY` class. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return { - re.ResourceControlled.resource_rep( - re.ResourceRY, {}, num_ctrl_wires + 1, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a single qubit rotation produces a sum of rotations. - The resources simplify to just one total single qubit rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - -class ResourceCRZ(qml.CRZ, re.ResourceOperator): - r"""Resource class for the CRZ gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int]): the wire the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are taken from Figure 1b of `Gheorghiu, V., Mosca, M. & Mukhopadhyay - `_. In combination with the following identity: - - .. math:: \hat{RZ}(\theta) = \hat{X} \cdot \hat{RZ}(- \theta) \cdot \hat{X}. - - By replacing the :code:`X` gates with :code:`CNOT` gates, we obtain a controlled-version of this - identity. Thus we are able to constructively or destructively interfere the gates based on the value - of the control qubit. Specifically, the resources are defined as two :class:`~.ResourceCNOT` gates - and two :class:`~.ResourceRZ` gates. - - .. seealso:: :class:`~.CRZ` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceCRZ.resources() - {CNOT: 2, RZ: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are taken from Figure 1b of `Gheorghiu, V., Mosca, M. & Mukhopadhyay - `_. In combination with the following identity: - - .. math:: \hat{RZ}(\theta) = \hat{X} \cdot \hat{RZ}(- \theta) \cdot \hat{X}. - - By replacing the :code:`X` gates with :code:`CNOT` gates, we obtain a controlled-version of this - identity. Thus we are able to constructively or destructively interfere the gates based on the value - of the control qubit. Specifically, the resources are defined as two :class:`~.ResourceCNOT` gates - and two :class:`~.ResourceRZ` gates. - """ - gate_types = {} - - cnot = re.ResourceCNOT.resource_rep() - rz = re.ResourceRZ.resource_rep() - - gate_types[cnot] = 2 - gate_types[rz] = 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of a single qubit rotation changes the sign of the rotation angle, - thus the resources of the adjoint operation result in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed using the symbolic :class:`~.ResourceControlled`. The resources - are computed according to the :code:`controlled_resource_decomp()` of the base - :class:`~.ResourceRZ` class. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return { - re.ResourceControlled.resource_rep( - re.ResourceRZ, {}, num_ctrl_wires + 1, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a single qubit rotation produces a sum of rotations. - The resources simplify to just one total single qubit rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - -class ResourceCRot(qml.CRot, re.ResourceOperator): - r"""Resource class for the CRot gate. - - Args: - phi (float): rotation angle :math:`\phi` - theta (float): rotation angle :math:`\theta` - omega (float): rotation angle :math:`\omega` - wires (Sequence[int]): the wire the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are taken from Figure 1b of `Gheorghiu, V., Mosca, M. & Mukhopadhyay - `_. In combination with the following identity: - - .. math:: - - \begin{align} - \hat{RZ}(\theta) = \hat{X} \cdot \hat{RZ}(- \theta) \cdot \hat{X}, \\ - \hat{RY}(\theta) = \hat{X} \cdot \hat{RY}(- \theta) \cdot \hat{X}. - \end{align} - - This identity is applied along with some clever choices for the angle values to combine rotation; - the final circuit takes the form: - - .. code-block:: bash - - ctrl: ─────╭●─────────╭●─────────┤ - trgt: ──RZ─╰X──RZ──RY─╰X──RY──RZ─┤ - - .. seealso:: :class:`~.CRot` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceCRot.resources() - {CNOT: 2, RZ: 3, RY: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are taken from Figure 1b of `Gheorghiu, V., Mosca, M. & Mukhopadhyay - `_. In combination with the following identity: - - .. math:: - - \begin{align} - \hat{RZ}(\theta) = \hat{X} \cdot \hat{RZ}(- \theta) \cdot \hat{X}, \\ - \hat{RY}(\theta) = \hat{X} \cdot \hat{RY}(- \theta) \cdot \hat{X}. - \end{align} - - This identity is applied along with some clever choices for the angle values to combine rotation; - the final circuit takes the form: - - .. code-block:: bash - - ctrl: ─────╭●─────────╭●─────────┤ - trgt: ──RZ─╰X──RZ──RY─╰X──RY──RZ─┤ - - """ - gate_types = {} - - cnot = re.ResourceCNOT.resource_rep() - rz = re.ResourceRZ.resource_rep() - ry = re.ResourceRY.resource_rep() - - gate_types[cnot] = 2 - gate_types[rz] = 3 - gate_types[ry] = 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of a general rotation flips the sign of the rotation angle, - thus the resources of the adjoint operation result in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed using the symbolic :class:`~.ResourceControlled`. The resources - are computed according to the :code:`controlled_resource_decomp()` of the base - :class:`~.ResourceRot` class. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return { - re.ResourceControlled.resource_rep( - re.ResourceRot, {}, num_ctrl_wires + 1, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a general single qubit rotation produces a sum of rotations. - The resources simplify to just one total single qubit rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - -class ResourceControlledPhaseShift(qml.ControlledPhaseShift, re.ResourceOperator): - r"""Resource class for the ControlledPhaseShift gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int]): the wire the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are derived using the fact that a :class:`~.ResourcePhaseShift` gate is - identical to the :class:`~.ResourceRZ` gate up to some global phase. Furthermore, a controlled - global phase simplifies to a :class:`~.ResourcePhaseShift` gate. This gives rise to the - following identity: - - .. math:: CR_\phi(\phi) = (R_\phi(\phi/2) \otimes I) \cdot CNOT \cdot (I \otimes R_\phi(-\phi/2)) \cdot CNOT \cdot (I \otimes R_\phi(\phi/2)) - - Specifically, the resources are defined as two :class:`~.ResourceCNOT` gates and three - :class:`~.ResourceRZ` gates. - - .. seealso:: :class:`~.ControlledPhaseShift` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceControlledPhaseShift.resources() - {CNOT: 2, RZ: 3} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - gate_types = {} - - cnot = re.ResourceCNOT.resource_rep() - rz = re.ResourceRZ.resource_rep() - - gate_types[cnot] = 2 - gate_types[rz] = 3 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of a phase shift just flips the sign of the phase angle, - thus the resources of the adjoint operation result in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are expressed using the symbolic :class:`~.ResourceControlled`. The resources - are computed according to the :code:`controlled_resource_decomp()` of the base - :class:`~.ResourcePhaseShift` class. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return { - re.ResourceControlled.resource_rep( - re.ResourcePhaseShift, {}, num_ctrl_wires + 1, num_ctrl_values, num_work_wires - ): 1 - } - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a phase shift produces a sum of shifts. - The resources simplify to just one total phase shift operator. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} diff --git a/pennylane/labs/resource_estimation/ops/op_math/symbolic.py b/pennylane/labs/resource_estimation/ops/op_math/symbolic.py deleted file mode 100644 index a1f7d246375..00000000000 --- a/pennylane/labs/resource_estimation/ops/op_math/symbolic.py +++ /dev/null @@ -1,1251 +0,0 @@ -# Copyright 2025 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. -r"""Resource operators for symbolic operations.""" -from collections import defaultdict -from typing import Dict - -import pennylane.labs.resource_estimation as re -from pennylane import math -from pennylane.labs.resource_estimation.resource_container import _scale_dict -from pennylane.operation import Operation -from pennylane.ops.op_math.adjoint import AdjointOperation -from pennylane.ops.op_math.controlled import ControlledOp -from pennylane.ops.op_math.exp import Exp -from pennylane.ops.op_math.pow import PowOperation -from pennylane.ops.op_math.prod import Prod -from pennylane.pauli import PauliSentence - -# pylint: disable=too-many-ancestors,arguments-differ,protected-access,too-many-arguments,too-many-positional-arguments - - -class ResourceAdjoint(AdjointOperation, re.ResourceOperator): - r"""Resource class for the symbolic AdjointOperation. - - A symbolic class used to represent the adjoint of some base operation. - - Args: - base (~.operation.Operator): The operator that we want the adjoint of. - - Resource Parameters: - * base_class (Type[~.ResourceOperator]): the class type of the base operator that we want the adjoint of - * base_params (dict): the resource parameters required to extract the cost of the base operator - - Resources: - This symbolic operation represents the adjoint of some base operation. The resources are - determined as follows. If the base operation class :code:`base_class` implements the - :code:`.adjoint_resource_decomp()` method, then the resources are obtained from this. - - Otherwise, the adjoint resources are given as the adjoint of each operation in the - base operation's resources (via :code:`.resources()`). - - .. seealso:: :class:`~.ops.op_math.adjoint.AdjointOperation` - - **Example** - - The adjoint operation can be constructed like this: - - >>> qft = re.ResourceQFT(wires=range(3)) - >>> adjoint_qft = re.ResourceAdjoint(qft) - >>> adjoint_qft.resources(**adjoint_qft.resource_params) - defaultdict(, {Adjoint(Hadamard): 3, Adjoint(SWAP): 1, - Adjoint(ControlledPhaseShift): 3}) - - Alternatively, we can call the resources method on from the class: - - >>> re.ResourceAdjoint.resources( - ... base_class = re.ResourceQFT, - ... base_params = {"num_wires": 3}, - ... ) - defaultdict(, {Adjoint(Hadamard): 3, Adjoint(SWAP): 1, - Adjoint(ControlledPhaseShift): 3}) - - .. details:: - :title: Usage Details - - We can configure the resources for the adjoint of a base operation by modifying - its :code:`.adjoint_resource_decomp(**resource_params)` method. Consider for example this - custom PauliZ class, where the adjoint resources are not defined (this is the default - for a general :class:`~.ResourceOperator`). - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def adjoint_resource_decomp(cls): - raise re.ResourcesNotDefined - - When this method is not defined, the adjoint resources are computed by taking the - adjoint of the resources of the operation. - - >>> CustomZ.resources() - {S: 2} - >>> re.ResourceAdjoint.resources(CustomZ, {}) - defaultdict(, {Adjoint(S): 2}) - - We can update the adjoint resources with the observation that the PauliZ gate is self-adjoint, - so the resources should just be the same as the base operation: - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def adjoint_resource_decomp(cls): - return {cls.resource_rep(): 1} - - >>> re.ResourceAdjoint.resources(CustomZ, {}) - {CustomZ: 1} - - """ - - @classmethod - def _resource_decomp( - cls, base_class, base_params, **kwargs - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - base_class (Type[~.ResourceOperator]): the class type of the base operator that we want the adjoint of - base_params (dict): the resource parameters required to extract the cost of the base operator - - Resources: - This symbolic operation represents the adjoint of some base operation. The resources are - determined as follows. If the base operation class :code:`base_class` implements the - :code:`.adjoint_resource_decomp()` method, then the resources are obtained from this. - - Otherwise, the adjoint resources are given as the adjoint of each operation in the - base operation's resources (via :code:`.resources()`). - - **Example** - - The adjoint operation can be constructed like this: - - >>> qft = re.ResourceQFT(wires=range(3)) - >>> adjoint_qft = re.ResourceAdjoint(qft) - >>> adjoint_qft.resources(**adjoint_qft.resource_params) - defaultdict(, {Adjoint(Hadamard): 3, Adjoint(SWAP): 1, - Adjoint(ControlledPhaseShift): 3}) - - Alternatively, we can call the resources method on from the class: - - >>> re.ResourceAdjoint.resources( - ... base_class = re.ResourceQFT, - ... base_params = {"num_wires": 3}, - ... ) - defaultdict(, {Adjoint(Hadamard): 3, Adjoint(SWAP): 1, - Adjoint(ControlledPhaseShift): 3}) - - .. details:: - :title: Usage Details - - We can configure the resources for the adjoint of a base operation by modifying - its :code:`.adjoint_resource_decomp(**resource_params)` method. Consider for example this - custom PauliZ class, where the adjoint resources are not defined (this is the default - for a general :class:`~.ResourceOperator`). - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def adjoint_resource_decomp(cls): - raise re.ResourcesNotDefined - - When this method is not defined, the adjoint resources are computed by taking the - adjoint of the resources of the operation. - - >>> CustomZ.resources() - {S: 2} - >>> re.ResourceAdjoint.resources(CustomZ, {}) - defaultdict(, {Adjoint(S): 2}) - - We can update the adjoint resources with the observation that the PauliZ gate is self-adjoint, - so the resources should just be the same as the base operation: - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def adjoint_resource_decomp(cls): - return {cls.resource_rep(): 1} - - >>> re.ResourceAdjoint.resources(CustomZ, {}) - {CustomZ: 1} - """ - try: - return base_class.adjoint_resource_decomp(**base_params) - except re.ResourcesNotDefined: - gate_types = defaultdict(int) - decomp = base_class.resources(**base_params, **kwargs) - for gate, count in decomp.items(): - rep = cls.resource_rep(gate.op_type, gate.params) - gate_types[rep] = count - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * base_class (Type[~.ResourceOperator]): the class type of the base operator that we want the adjoint of - * base_params (dict): the resource parameters required to extract the cost of the base operator - - """ - return {"base_class": type(self.base), "base_params": self.base.resource_params} - - @classmethod - def resource_rep(cls, base_class, base_params) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - base_class (Type[~.ResourceOperator]): the class type of the base operator that we want the adjoint of - base_params (dict): the resource parameters required to extract the cost of the base operator - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - return re.CompressedResourceOp(cls, {"base_class": base_class, "base_params": base_params}) - - @staticmethod - def adjoint_resource_decomp(base_class, base_params) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Args: - base_class (Type[~.ResourceOperator]): the class type of the base operator that we want the adjoint of - base_params (dict): the resource parameters required to extract the cost of the base operator - - Resources: - The adjoint of an adjointed operation is just the original operation. The resources - are given as one instance of the base operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {base_class.resource_rep(**base_params): 1} - - @staticmethod - def tracking_name(base_class, base_params) -> str: - r"""Returns the tracking name built with the operator's parameters.""" - base_name = base_class.tracking_name(**base_params) - return f"Adjoint({base_name})" - - -class ResourceControlled(ControlledOp, re.ResourceOperator): - r"""Resource class for the symbolic ControlledOp. - - A symbolic class used to represent the application of some base operation controlled on the state - of some control qubits. - - Args: - base (~.operation.Operator): the operator that is controlled - control_wires (Any): The wires to control on. - control_values (Iterable[Bool]): The values to control on. Must be the same - length as ``control_wires``. Defaults to ``True`` for all control wires. - Provided values are converted to `Bool` internally. - work_wires (Any): Any auxiliary wires that can be used in the decomposition - - Resource Parameters: - * base_class (Type[~.ResourceOperator]): the class type of the base operator to be controlled - * base_params (dict): the resource parameters required to extract the cost of the base operator - * num_ctrl_wires (int): the number of qubits the operation is controlled on - * num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - * num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are determined as follows. If the base operation class :code:`base_class` - implements the :code:`.controlled_resource_decomp()` method, then the resources are obtained - directly from this. - - Otherwise, the controlled resources are given in two steps. Firstly, any control qubits which - should be triggered when in the :math:`|0\rangle` state, are flipped. This corresponds to an additional - cost of two :class:`~.ResourceX` gates per :code:`num_ctrl_values`. Secondly, the base operation - resources are extracted (via :code:`.resources()`) and we add to the cost the controlled - variant of each operation in the resources. - - .. seealso:: :class:`~.ops.op_math.controlled.ControlledOp` - - **Example** - - The controlled operation can be constructed like this: - - >>> qft = re.ResourceQFT(wires=range(3)) - >>> controlled_qft = re.ResourceControlled( - ... qft, control_wires=['c0', 'c1', 'c2'], control_values=[1, 1, 1], work_wires=['w1', 'w2'], - ... ) - >>> controlled_qft.resources(**controlled_qft.resource_params) - defaultdict(, {C(Hadamard,3,0,2): 3, C(SWAP,3,0,2): 1, C(ControlledPhaseShift,3,0,2): 3}) - - Alternatively, we can call the resources method on from the class: - - >>> re.ResourceControlled.resources( - ... base_class = re.ResourceQFT, - ... base_params = {"num_wires": 3}, - ... num_ctrl_wires = 3, - ... num_ctrl_values = 0, - ... num_work_wires = 2, - ... ) - defaultdict(, {C(Hadamard,3,0,2): 3, C(SWAP,3,0,2): 1, C(ControlledPhaseShift,3,0,2): 3}) - - .. details:: - :title: Usage Details - - We can configure the resources for the controlled of a base operation by modifying - its :code:`.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires, - **resource_params)` method. Consider for example this custom PauliZ class, where the - controlled resources are not defined (this is the default for a general :class:`~.ResourceOperator`). - - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def controlled_resource_decomp(cls, num_ctrl_wires, num_ctrl_values, num_work_wires): - raise re.ResourcesNotDefined - - When this method is not defined, the controlled resources are computed by taking the - controlled of each operation in the resources of the base operation. - - >>> CustomZ.resources() - {S: 2} - >>> re.ResourceControlled.resources(CustomZ, {}, num_ctrl_wires=1, num_ctrl_values=0, num_work_wires=0) - defaultdict(, {C(S,2,0,3): 2}) - - We can update the controlled resources with the observation that the PauliZ gate when controlled - on a single wire is equivalent to :math:`\hat{CZ} = \hat{H} \cdot \hat{CNOT} \cdot \hat{H}`. - so we can modify the base operation: - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def controlled_resource_decomp(cls, num_ctrl_wires, num_ctrl_values, num_work_wires): - if num_ctrl_wires == 1 and num_ctrl_values == 0: - return { - re.ResourceHadamard.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 1, - } - raise re.ResourcesNotDefined - - >>> re.ResourceControlled.resources(CustomZ, {}, num_ctrl_wires=1, num_ctrl_values=0, num_work_wires=0) - {Hadamard: 2, CNOT: 1} - - """ - - @classmethod - def _resource_decomp( - cls, base_class, base_params, num_ctrl_wires, num_ctrl_values, num_work_wires, **kwargs - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - base_class (Type[~.ResourceOperator]): the class type of the base operator to be controlled - base_params (dict): the resource parameters required to extract the cost of the base operator - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are determined as follows. If the base operation class :code:`base_class` - implements the :code:`.controlled_resource_decomp()` method, then the resources are obtained - directly from this. - - Otherwise, the controlled resources are given in two steps. Firstly, any control qubits which - should be triggered when in the :math:`|0\rangle` state, are flipped. This corresponds to an additional - cost of two :class:`~.ResourceX` gates per :code:`num_ctrl_values`. Secondly, the base operation - resources are extracted (via :code:`.resources()`) and we add to the cost the controlled - variant of each operation in the resources. - - .. seealso:: :class:`~.ops.op_math.controlled.ControlledOp` - - **Example** - - The controlled operation can be constructed like this: - - >>> qft = re.ResourceQFT(wires=range(3)) - >>> controlled_qft = re.ResourceControlled( - ... qft, control_wires=['c0', 'c1', 'c2'], control_values=[1, 1, 1], work_wires=['w1', 'w2'], - ... ) - >>> controlled_qft.resources(**controlled_qft.resource_params) - defaultdict(, {C(Hadamard,3,0,2): 3, C(SWAP,3,0,2): 1, C(ControlledPhaseShift,3,0,2): 3}) - - Alternatively, we can call the resources method on from the class: - - >>> re.ResourceControlled.resources( - ... base_class = re.ResourceQFT, - ... base_params = {"num_wires": 3}, - ... num_ctrl_wires = 3, - ... num_ctrl_values = 0, - ... num_work_wires = 2, - ... ) - defaultdict(, {C(Hadamard,3,0,2): 3, C(SWAP,3,0,2): 1, C(ControlledPhaseShift,3,0,2): 3}) - - .. details:: - :title: Usage Details - - We can configure the resources for the controlled of a base operation by modifying - its :code:`.controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires, - **resource_params)` method. Consider for example this custom PauliZ class, where the - controlled resources are not defined (this is the default for a general :class:`~.ResourceOperator`). - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def controlled_resource_decomp(cls, num_ctrl_wires, num_ctrl_values, num_work_wires): - raise re.ResourcesNotDefined - - When this method is not defined, the controlled resources are computed by taking the - controlled of each operation in the resources of the base operation. - - >>> CustomZ.resources() - {S: 2} - >>> re.ResourceControlled.resources(CustomZ, {}, num_ctrl_wires=1, num_ctrl_values=0, num_work_wires=0) - defaultdict(, {C(S,2,0,3): 2}) - - We can update the controlled resources with the observation that the PauliZ gate when controlled - on a single wire is equivalent to :math:`\hat{CZ} = \hat{H} \cdot \hat{CNOT} \cdot \hat{H}`. - so we can modify the base operation: - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def controlled_resource_decomp(cls, num_ctrl_wires, num_ctrl_values, num_work_wires): - if num_ctrl_wires == 1 and num_ctrl_values == 0: - return { - re.ResourceHadamard.resource_rep(): 2, - re.ResourceCNOT.resource_rep(): 1, - } - raise re.ResourcesNotDefined - - >>> re.ResourceControlled.resources(CustomZ, {}, num_ctrl_wires=1, num_ctrl_values=0, num_work_wires=0) - {Hadamard: 2, CNOT: 1} - - """ - try: - return base_class.controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires, **base_params - ) - except re.ResourcesNotDefined: - pass - - gate_types = defaultdict(int) - - if num_ctrl_values == 0: - decomp = base_class.resources(**base_params, **kwargs) - for gate, count in decomp.items(): - rep = cls.resource_rep(gate.op_type, gate.params, num_ctrl_wires, 0, num_work_wires) - gate_types[rep] = count - - return gate_types - - no_control = cls.resource_rep(base_class, base_params, num_ctrl_wires, 0, num_work_wires) - x = re.ResourceX.resource_rep() - gate_types[no_control] = 1 - gate_types[x] = 2 * num_ctrl_values - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * base_class (Type[~.ResourceOperator]): the class type of the base operator to be controlled - * base_params (dict): the resource parameters required to extract the cost of the base operator - * num_ctrl_wires (int): the number of qubits the operation is controlled on - * num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - * num_work_wires (int): the number of additional qubits that can be used for decomposition - """ - return { - "base_class": type(self.base), - "base_params": self.base.resource_params, - "num_ctrl_wires": len(self.control_wires), - "num_ctrl_values": len([val for val in self.control_values if not val]), - "num_work_wires": len(self.work_wires), - } - - @classmethod - def resource_rep( - cls, base_class, base_params, num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - base_class (Type[~.ResourceOperator]): the class type of the base operator to be controlled - base_params (dict): the resource parameters required to extract the cost of the base operator - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - return re.CompressedResourceOp( - cls, - { - "base_class": base_class, - "base_params": base_params, - "num_ctrl_wires": num_ctrl_wires, - "num_ctrl_values": num_ctrl_values, - "num_work_wires": num_work_wires, - }, - ) - - @classmethod - def controlled_resource_decomp( - cls, - outer_num_ctrl_wires, - outer_num_ctrl_values, - outer_num_work_wires, - base_class, - base_params, - num_ctrl_wires, - num_ctrl_values, - num_work_wires, - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - outer_num_ctrl_wires (int): The number of control qubits to further control the base - controlled operation upon. - outer_num_ctrl_values (int): The subset of those control qubits, which further control - the base controlled operation, which are controlled when in the :math:`|0\rangle` state. - outer_num_work_wires (int): the number of additional qubits that can be used in the - decomposition for the further controlled, base control oepration. - base_class (Type[~.ResourceOperator]): the class type of the base operator to be controlled - base_params (dict): the resource parameters required to extract the cost of the base operator - num_ctrl_wires (int): the number of control qubits of the operation - num_ctrl_values (int): The subset of control qubits of the operation, that are controlled - when in the :math:`|0\rangle` state. - num_work_wires (int): The number of additional qubits that can be used for the - decomposition of the operation. - - Resources: - The resources are derived by simply combining the control qubits, control-values and - work qubits into a single instance of :class:`~.ResourceControlled` gate, controlled - on the whole set of control-qubits. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return { - cls.resource_rep( - base_class, - base_params, - outer_num_ctrl_wires + num_ctrl_wires, - outer_num_ctrl_values + num_ctrl_values, - outer_num_work_wires + num_work_wires, - ): 1 - } - - @staticmethod - def tracking_name(base_class, base_params, num_ctrl_wires, num_ctrl_values, num_work_wires): - r"""Returns the tracking name built with the operator's parameters.""" - base_name = base_class.tracking_name(**base_params) - return f"C({base_name},{num_ctrl_wires},{num_ctrl_values},{num_work_wires})" - - -class ResourcePow(PowOperation, re.ResourceOperator): - r"""Resource class for the symbolic Pow operation. - - A symbolic class used to represent some base operation raised to a power. - - Args: - base (~.operation.Operator): the operator to be raised to a power - z (float): the exponent (default value is 1) - - Resource Parameters: - * base_class (Type[~.ResourceOperator]): The class type of the base operator to be raised to some power. - * base_params (dict): the resource parameters required to extract the cost of the base operator - * z (int): the power that the operator is being raised to - - Resources: - The resources are determined as follows. If the power :math:`z = 0`, then we have the identitiy - gate and we have no resources. If the base operation class :code:`base_class` implements the - :code:`.pow_resource_decomp()` method, then the resources are obtained from this. Otherwise, - the resources of the operation raised to the power :math:`z` are given by extracting the base - operation's resources (via :code:`.resources()`) and raising each operation to the same power. - - .. seealso:: :class:`~.ops.op_math.pow.PowOperation` - - **Example** - - The operation raised to a power :math:`z` can be constructed like this: - - >>> qft = re.ResourceQFT(wires=range(3)) - >>> pow_qft = re.ResourcePow(qft, 2) - >>> pow_qft.resources(**pow_qft.resource_params) - defaultdict(, {Pow(Hadamard, 2): 3, Pow(SWAP, 2): 1, Pow(ControlledPhaseShift, 2): 3}) - - Alternatively, we can call the resources method on from the class: - - >>> re.ResourcePow.resources( - ... base_class = re.ResourceQFT, - ... base_params = {"num_wires": 3}, - ... z = 2, - ... ) - defaultdict(, {Pow(Hadamard, 2): 3, Pow(SWAP, 2): 1, Pow(ControlledPhaseShift, 2): 3}) - - .. details:: - :title: Usage Details - - We can configure the resources for the power of a base operation by modifying - its :code:`.pow_resource_decomp(**resource_params, z)` method. Consider for example this - custom PauliZ class, where the pow-resources are not defined (this is the default - for a general :class:`~.ResourceOperator`). - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def pow_resource_decomp(cls, z): - raise re.ResourcesNotDefined - - When this method is not defined, the resources are computed by taking the power of - each operation in the resources of the base operation. - - >>> CustomZ.resources() - {S: 2} - >>> re.ResourcePow.resources(CustomZ, {}, z=2) - defaultdict(, {Pow(S, 2): 2}) - - We can update the resources with the observation that the PauliZ gate is self-inverse, - so the resources should when :math:`z mod 2 = 0` should just be the identity operation: - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def pow_resource_decomp(cls, z): - if z%2 == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} - - >>> re.ResourcePow.resources(CustomZ, {}, z=2) - {Identity: 1} - >>> re.ResourcePow.resources(CustomZ, {}, z=3) - {CustomZ: 1} - - """ - - @classmethod - def _resource_decomp( - cls, base_class, base_params, z, **kwargs - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - base_class (Type[~.ResourceOperator]): The class type of the base operator to be raised to some power. - base_params (dict): the resource parameters required to extract the cost of the base operator - z (int): the power that the operator is being raised to - - Resources: - The resources are determined as follows. If the power :math:`z = 0`, then we have the identitiy - gate and we have no resources. If the base operation class :code:`base_class` implements the - :code:`.pow_resource_decomp()` method, then the resources are obtained from this. Otherwise, - the resources of the operation raised to the power :math:`z` are given by extracting the base - operation's resources (via :code:`.resources()`) and raising each operation to the same power. - - **Example** - - The operation raised to a power :math:`z` can be constructed like this: - - >>> qft = re.ResourceQFT(wires=range(3)) - >>> pow_qft = re.ResourcePow(qft, 2) - >>> pow_qft.resources(**pow_qft.resource_params) - defaultdict(, {Pow(Hadamard, 2): 3, Pow(SWAP, 2): 1, Pow(ControlledPhaseShift, 2): 3}) - - Alternatively, we can call the resources method on from the class: - - >>> re.ResourcePow.resources( - ... base_class = re.ResourceQFT, - ... base_params = {"num_wires": 3}, - ... z = 2, - ... ) - defaultdict(, {Pow(Hadamard, 2): 3, Pow(SWAP, 2): 1, Pow(ControlledPhaseShift, 2): 3}) - - .. details:: - :title: Usage Details - - We can configure the resources for the power of a base operation by modifying - its :code:`.pow_resource_decomp(**resource_params, z)` method. Consider for example this - custom PauliZ class, where the pow-resources are not defined (this is the default - for a general :class:`~.ResourceOperator`). - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def pow_resource_decomp(cls, z): - raise re.ResourcesNotDefined - - When this method is not defined, the resources are computed by taking the power of - each operation in the resources of the base operation. - - >>> CustomZ.resources() - {S: 2} - >>> re.ResourcePow.resources(CustomZ, {}, z=2) - defaultdict(, {Pow(S, 2): 2}) - - We can update the resources with the observation that the PauliZ gate is self-inverse, - so the resources should when :math:`z mod 2 = 0` should just be the identity operation: - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def pow_resource_decomp(cls, z): - if z%2 == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} - - >>> re.ResourcePow.resources(CustomZ, {}, z=2) - {Identity: 1} - >>> re.ResourcePow.resources(CustomZ, {}, z=3) - {CustomZ: 1} - - """ - if z == 0: - return {re.ResourceIdentity.resource_rep(): 1} - - if z == 1: - return {base_class.resource_rep(**base_params): 1} - - try: - return base_class.pow_resource_decomp(z, **base_params) - except re.ResourcesNotDefined: - pass - - try: - gate_types = defaultdict(int) - decomp = base_class.resources(**base_params, **kwargs) - for gate, count in decomp.items(): - rep = cls.resource_rep(gate.op_type, gate.params, z) - gate_types[rep] = count - - return gate_types - except re.ResourcesNotDefined: - pass - - return {base_class.resource_rep(**base_params): z} - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * base_class (Type[~.ResourceOperator]): The class type of the base operator to be raised to some power. - * base_params (dict): the resource parameters required to extract the cost of the base operator - * z (int): the power that the operator is being raised to - """ - return { - "base_class": type(self.base), - "base_params": self.base.resource_params, - "z": self.z, - } - - @classmethod - def resource_rep(cls, base_class, base_params, z) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - base_class (Type[~.ResourceOperator]): The class type of the base operator to be raised to some power. - base_params (dict): the resource parameters required to extract the cost of the base operator - z (int): the power that the operator is being raised to - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - return re.CompressedResourceOp( - cls, {"base_class": base_class, "base_params": base_params, "z": z} - ) - - @classmethod - def pow_resource_decomp( - cls, z0, base_class, base_params, z - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z0 (int): the power that the power-operator is being raised to - base_class (Type[~.ResourceOperator]): The class type of the base operator to be raised to some power. - base_params (dict): The resource parameters required to extract the cost of the base operator. - z (int): the power that the base operator is being raised to - - Resources: - The resources are derived by simply adding together the :math:`z` exponent and the - :math:`z_{0}` exponent into a single instance of :class:`~.ResourcePow` gate, raising - the base operator to the power :math:`z + z_{0}`. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(base_class, base_params, z0 * z): 1} - - @staticmethod - def tracking_name(base_class, base_params, z) -> str: - r"""Returns the tracking name built with the operator's parameters.""" - base_name = base_class.tracking_name(**base_params) - return f"Pow({base_name}, {z})" - - -class ResourceExp(Exp, re.ResourceOperator): - r"""Resource class for the symbolic Exp operation. - - A symbolic class used to represent the exponential of some base operation. - - Args: - base (~.operation.Operator): The operator to be exponentiated - coeff=1 (Number): A scalar coefficient of the operator. - num_steps (int): The number of steps used in the decomposition of the exponential operator, - also known as the Trotter number. If this value is `None` and the Suzuki-Trotter - decomposition is needed, an error will be raised. - id (str): id for the Exp operator. Default is None. - - Resource Parameters: - * base_class (Type[~.ResourceOperator]): The class type of the base operator that is exponentiated. - * base_params (dict): the resource parameters required to extract the cost of the base operator - * base_pauli_rep (Union[PauliSentence, None]): The base operator represented as a linear combination of Pauli words. If such a representation is not applicable, then :code:`None`. - * coeff (complex): a scalar value which multiplies the base operator in the exponent - * num_steps (int): the number of trotter steps to use in approximating the exponential - - Resources: - This symbolic operation represents the exponential of some base operation. The resources - are determined as follows. If the base operation class :code:`base_class` implements the - :code:`.exp_resource_decomp()` method, then the resources are obtained from this. - - Otherwise, the exponetiated operator's resources are computed using the linear combination - of Pauli words representation (:code:`base_pauli_rep`). The exponential is approximated by - the product of the exponential of each Pauli word in the sum. This product is repeated - :code:`num_steps` many times. Specifically, the cost for the exponential of each Pauli word - is given by an associated :class:`~.ResourcePauliRot`. - - .. seealso:: :class:`~.ops.op_math.exp.Exp` - - **Example** - - The exponentiated operation can be constructed like this: - - >>> hamiltonian = qml.dot([0.1, -2.3], [qml.X(0)@qml.Y(1), qml.Z(0)]) - >>> hamiltonian - 0.1 * (X(0) @ Y(1)) + -2.3 * Z(0) - >>> exp_hamiltonian = re.ResourceExp(hamiltonian, 0.1*1j, num_steps=2) - >>> exp_hamiltonian.resources(**exp_hamiltonian.resource_params) - defaultdict(, {PauliRot: 2, PauliRot: 2}) - - Alternatively, we can call the resources method on from the class: - - >>> re.ResourceExp.resources( - ... base_class = qml.ops.Sum, - ... base_params = {}, - ... base_pauli_rep = hamiltonian.pauli_rep, - ... coeff = 0.1*1j, - ... num_steps = 2, - ... ) - defaultdict(, {PauliRot: 2, PauliRot: 2}) - - .. details:: - :title: Usage Details - - We can configure the resources for the exponential of a base operation by modifying - its :code:`.exp_resource_decomp(scalar, num_steps, **resource_params)` method. Consider - for example this custom PauliZ class, where the exponentiated resources are not defined - (this is the default for a general :class:`~.ResourceOperator`). - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def exp_resource_decomp(cls, scalar, num_steps): - raise re.ResourcesNotDefined - - When this method is not defined, the resources are computed from the linear combination - of Pauli words representation. - - >>> pauli_rep = CustomZ(wires=0).pauli_rep - >>> pauli_rep - 1.0 * Z(0) - >>> re.ResourceExp.resources(CustomZ, {}, base_pauli_rep=pauli_rep, coeff=0.1*1j, num_steps=3) - defaultdict(, {PauliRot: 3}) - - We can update the exponential resources with the observation that the PauliZ gate, when - exponentiated, produces an RZ rotation: - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def exp_resource_decomp(cls, scalar, num_steps): - return {re.ResourceRZ.resource_rep(): num_steps} - - >>> re.ResourceExp.resources(CustomZ, {}, base_pauli_rep=pauli_rep, coeff=0.1*1j, num_steps=3) - {RZ: 3} - - """ - - @staticmethod - def _resource_decomp( - base_class: Operation, - base_params: Dict, - base_pauli_rep: PauliSentence, - coeff: complex, - num_steps: int, - **kwargs, - ): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - base_class (Type[~.ResourceOperator]): The class type of the base operator that is - exponentiated. - base_params (dict): the resource parameters required to extract the cost of the base operator - base_pauli_rep (Union[PauliSentence, None]): The base operator represented as a linear - combination of Pauli words. If such a representation is not applicable, then :code:`None`. - coeff (complex): a scalar value which multiplies the base operator in the exponent - num_steps (int): the number of trotter steps to use in approximating the exponential - - Resources: - This symbolic operation represents the exponential of some base operation. The resources - are determined as follows. If the base operation class :code:`base_class` implements the - :code:`.exp_resource_decomp()` method, then the resources are obtained from this. - - Otherwise, the exponetiated operator's resources are computed using the linear combination - of Pauli words representation (:code:`base_pauli_rep`). The exponential is approximated by - the product of the exponential of each Pauli word in the sum. This product is repeated - :code:`num_steps` many times. Specifically, the cost for the exponential of each Pauli word - is given by an associated :class:`~.ResourcePauliRot`. - - **Example** - - The exponentiated operation can be constructed like this: - - >>> hamiltonian = qml.dot([0.1, -2.3], [qml.X(0)@qml.Y(1), qml.Z(0)]) - >>> hamiltonian - 0.1 * (X(0) @ Y(1)) + -2.3 * Z(0) - >>> exp_hamiltonian = re.ResourceExp(hamiltonian, 0.1*1j, num_steps=2) - >>> exp_hamiltonian.resources(**exp_hamiltonian.resource_params) - defaultdict(, {PauliRot: 2, PauliRot: 2}) - - Alternatively, we can call the resources method on from the class: - - >>> re.ResourceExp.resources( - ... base_class = qml.ops.Sum, - ... base_params = {}, - ... base_pauli_rep = hamiltonian.pauli_rep, - ... coeff = 0.1*1j, - ... num_steps = 2, - ... ) - defaultdict(, {PauliRot: 2, PauliRot: 2}) - - .. details:: - :title: Usage Details - - We can configure the resources for the exponential of a base operation by modifying - its :code:`.exp_resource_decomp(scalar, num_steps, **resource_params)` method. Consider - for example this custom PauliZ class, where the exponentiated resources are not defined - (this is the default for a general :class:`~.ResourceOperator`). - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def exp_resource_decomp(cls, scalar, num_steps): - raise re.ResourcesNotDefined - - When this method is not defined, the resources are computed from the linear combination - of Pauli words representation. - - >>> pauli_rep = CustomZ(wires=0).pauli_rep - >>> pauli_rep - 1.0 * Z(0) - >>> re.ResourceExp.resources(CustomZ, {}, base_pauli_rep=pauli_rep, coeff=0.1*1j, num_steps=3) - defaultdict(, {PauliRot: 3}) - - We can update the exponential resources with the observation that the PauliZ gate, when - exponentiated, produces an RZ rotation: - - .. code-block:: python - - class CustomZ(re.ResourceZ): - - @classmethod - def exp_resource_decomp(cls, scalar, num_steps): - return {re.ResourceRZ.resource_rep(): num_steps} - - >>> re.ResourceExp.resources(CustomZ, {}, base_pauli_rep=pauli_rep, coeff=0.1*1j, num_steps=3) - {RZ: 3} - - """ - # Custom exponential operator resources: - if issubclass(base_class, re.ResourceOperator): - try: - return base_class.exp_resource_decomp(coeff, num_steps, **base_params) - except re.ResourcesNotDefined: - pass - - if base_pauli_rep and math.real(coeff) == 0: - scalar = num_steps or 1 # 1st-order Trotter-Suzuki with 'num_steps' trotter steps: - return _scale_dict( - _resources_from_pauli_sentence(base_pauli_rep), scalar=scalar, in_place=True - ) - - raise re.ResourcesNotDefined - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * base_class (Type[ResourceOperator]): The class type of the base operator that is exponentiated. - * base_params (dict): the resource parameters required to extract the cost of the base operator - * base_pauli_rep (Union[PauliSentence, None]): The base operator represented as a linear combination of Pauli words. If such a representation is not applicable, then :code:`None`. - * coeff (complex): a scalar value which multiplies the base operator in the exponent - * num_steps (int): the number of trotter steps to use in approximating the exponential - """ - return _extract_exp_params(self.base, self.scalar, self.num_steps) - - @classmethod - def resource_rep(cls, base_class, base_params, base_pauli_rep, coeff, num_steps): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - base_class (Type[~.ResourceOperator]): The class type of the base operator that is - exponentiated. - base_params (dict): the resource parameters required to extract the cost of the base operator - base_pauli_rep (Union[PauliSentence, None]): The base operator represented as a linear - combination of Pauli words. If such a representation is not applicable, then :code:`None`. - coeff (complex): a scalar value which multiplies the base operator in the exponent - num_steps (int): the number of trotter steps to use in approximating the exponential - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - name = cls.tracking_name(base_class, base_params, base_pauli_rep, coeff, num_steps) - return re.CompressedResourceOp( - cls, - { - "base_class": base_class, - "base_params": base_params, - "base_pauli_rep": base_pauli_rep, - "coeff": coeff, - "num_steps": num_steps, - }, - name=name, - ) - - @classmethod - def pow_resource_decomp( - cls, z0, base_class, base_params, base_pauli_rep, coeff, num_steps - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z0 (int): the power that the operator is being raised to - base_class (Type[~.ResourceOperator]): The class type of the base operator that is - exponentiated. - base_params (dict): the resource parameters required to extract the cost of the base operator - base_pauli_rep (Union[PauliSentence, None]): The base operator represented as a linear - combination of Pauli words. If such a representation is not applicable, then :code:`None`. - coeff (complex): a scalar value which multiplies the base operator in the exponent - num_steps (int): the number of trotter steps to use in approximating the exponential - - Resources: - The resources are derived by simply multiplying together the :math:`z0` exponent and the - :code:`coeff` coefficient into a single instance of :class:`~.ResourceExp` gate with - coefficient :code:`z0 * coeff`. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(base_class, base_params, base_pauli_rep, z0 * coeff, num_steps): 1} - - @staticmethod - def tracking_name( - base_class: Operation, - base_params: Dict, - base_pauli_rep: PauliSentence, - coeff: complex, - num_steps: int, - ): # pylint: disable=unused-argument - r"""Returns the tracking name built with the operator's parameters.""" - base_name = ( - base_class.tracking_name(**base_params) - if issubclass(base_class, re.ResourceOperator) - else base_class.__name__ - ) - - return f"Exp({base_name}, {coeff}, num_steps={num_steps})".replace("Resource", "") - - -class ResourceProd(Prod, re.ResourceOperator): - r"""Resource class for the symbolic Prod operation. - - A symbolic class used to represent a product of some base operations. - - Args: - *factors (tuple[~.operation.Operator]): a tuple of operators which will be multiplied together. - - Resource Parameters: - * cmpr_factors (list[CompressedResourceOp]): A list of operations, in the compressed representation, corresponding to the factors in the product. - - Resources: - This symbolic class represents a product of operations. The resources are defined trivially as the counts for each operation in the product. - - .. seealso:: :class:`~.ops.op_math.prod.Prod` - - **Example** - - The product of operations can be constructed as follows. Note, each operation in the - product must be a valid :class:`~.ResourceOperator` - - >>> prod_op = re.ResourceProd( - ... re.ResourceQFT(range(3)), - ... re.ResourceZ(0), - ... re.ResourceGlobalPhase(1.23, wires=[1]) - ... ) - >>> prod_op - ResourceQFT(wires=[0, 1, 2]) @ Z(0) @ ResourceGlobalPhase(1.23, wires=[1]) - >>> prod_op.resources(**prod_op.resource_params) - defaultdict(, {QFT(3): 1, Z: 1, GlobalPhase: 1}) - - """ - - @staticmethod - def _resource_decomp(cmpr_factors, **kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - cmpr_factors (list[CompressedResourceOp]): A list of operations, in the compressed - representation, corresponding to the factors in the product. - - Resources: - This symbolic class represents a product of operations. The resources are defined - trivially as the counts for each operation in the product. - - .. seealso:: :class:`~.ops.op_math.prod.Prod` - - **Example** - - The product of operations can be constructed as follows. Note, each operation in the - product must be a valid :class:`~.ResourceOperator` - - >>> prod_op = re.ResourceProd( - ... re.ResourceQFT(range(3)), - ... re.ResourceZ(0), - ... re.ResourceGlobalPhase(1.23, wires=[1]) - ... ) - >>> prod_op - ResourceQFT(wires=[0, 1, 2]) @ Z(0) @ ResourceGlobalPhase(1.23, wires=[1]) - >>> prod_op.resources(**prod_op.resource_params) - defaultdict(, {QFT(3): 1, Z: 1, GlobalPhase: 1}) - - """ - res = defaultdict(int) - for factor in cmpr_factors: - res[factor] += 1 - return res - - @property - def resource_params(self) -> Dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * cmpr_factors (list[CompressedResourceOp]): A list of operations, in the compressed representation, corresponding to the factors in the product. - """ - try: - cmpr_factors = tuple(factor.resource_rep_from_op() for factor in self.operands) - except AttributeError as error: - raise ValueError( - "All factors of the Product must be instances of `ResourceOperator` in order to obtain resources." - ) from error - - return {"cmpr_factors": cmpr_factors} - - @classmethod - def resource_rep(cls, cmpr_factors) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - cmpr_factors (list[CompressedResourceOp]): A list of operations, in the compressed - representation, corresponding to the factors in the product. - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - return re.CompressedResourceOp(cls, {"cmpr_factors": cmpr_factors}) - - -def _extract_exp_params(base_op, scalar, num_steps): - pauli_rep = base_op.pauli_rep - isinstance_resource_op = isinstance(base_op, re.ResourceOperator) - - if (not isinstance_resource_op) and (pauli_rep is None): - raise ValueError( - f"Cannot obtain resources for the exponential of {base_op}, if it is not a ResourceOperator and it doesn't have a Pauli decomposition." - ) - - base_class = type(base_op) - base_params = base_op.resource_params if isinstance_resource_op else {} - - return { - "base_class": base_class, - "base_params": base_params, - "base_pauli_rep": pauli_rep, - "coeff": scalar, - "num_steps": num_steps, - } - - -def _resources_from_pauli_sentence(pauli_sentence): - gate_types = defaultdict(int) - - for pauli_word in iter(pauli_sentence.keys()): - pauli_string = "".join((str(v) for v in pauli_word.values())) - pauli_rot_gate = re.ResourcePauliRot.resource_rep(pauli_string) - gate_types[pauli_rot_gate] = 1 - - return gate_types diff --git a/pennylane/labs/resource_estimation/ops/qubit/__init__.py b/pennylane/labs/resource_estimation/ops/qubit/__init__.py deleted file mode 100644 index 7c012a8ff6d..00000000000 --- a/pennylane/labs/resource_estimation/ops/qubit/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# 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. -r"""This module contains experimental resource estimation functionality.""" - -from .non_parametric_ops import * -from .parametric_ops_multi_qubit import * -from .parametric_ops_single_qubit import * -from .qchem_ops import * diff --git a/pennylane/labs/resource_estimation/ops/qubit/non_parametric_ops.py b/pennylane/labs/resource_estimation/ops/qubit/non_parametric_ops.py deleted file mode 100644 index 6fcfea57750..00000000000 --- a/pennylane/labs/resource_estimation/ops/qubit/non_parametric_ops.py +++ /dev/null @@ -1,1024 +0,0 @@ -# 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. -r"""Resource operators for non parametric single qubit operations.""" -from typing import Dict - -import pennylane as qml -import pennylane.labs.resource_estimation as re - -# pylint: disable=arguments-differ - - -class ResourceHadamard(qml.Hadamard, re.ResourceOperator): - r"""Resource class for the Hadamard gate. - - Args: - wires (Sequence[int] or int): the wire the operation acts on - - Resources: - The Hadamard gate is treated as a fundamental gate and thus it cannot be decomposed - further. Requesting the resources of this gate raises a :code:`ResourcesNotDefined` error. - - .. seealso:: :class:`~.Hadamard` - - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The Hadamard gate is treated as a fundamental gate and thus it cannot be decomposed - further. Requesting the resources of this gate raises a :code:`ResourcesNotDefined` error. - - Raises: - ResourcesNotDefined: This gate is fundamental, no further decomposition defined. - """ - raise re.ResourcesNotDefined - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - For a single control wire, the cost is a single instance of :class:`~.ResourceCH`. - Two additional :class:`~.ResourceX` gates are used to flip the control qubit if - it is zero-controlled. - - In the case where multiple controlled wires are provided, the resources are derived from - the following identities (as presented in this `blog post `_): - - .. math:: - - \begin{align} - \hat{H} &= \hat{R}_{y}(\frac{\pi}{4}) \cdot \hat{Z} \cdot \hat{R}_{y}(\frac{-\pi}{4}), \\ - \hat{Z} &= \hat{H} \cdot \hat{X} \cdot \hat{H}. - \end{align} - - Specifically, the resources are given by two :class:`~.ResourceRY` gates, two - :class:`~.ResourceHadamard` gates and a :class:`~.ResourceX` gate. By replacing the - :class:`~.ResourceX` gate with :class:`~.ResourceMultiControlledX` gate, we obtain a - controlled-version of this identity. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1: - gate_types = {re.ResourceCH.resource_rep(): 1} - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 - - return gate_types - - gate_types = {} - - ry = re.ResourceRY.resource_rep() - h = re.ResourceHadamard.resource_rep() - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - gate_types[h] = 2 - gate_types[ry] = 2 - gate_types[mcx] = 1 - return gate_types - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - The Hadamard gate raised to even powers produces identity and raised - to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if z % 2 == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} - - -class ResourceS(qml.S, re.ResourceOperator): - r"""Resource class for the S-gate. - - Args: - wires (Sequence[int] or int): the wire the operation acts on - - Resources: - The S-gate decomposes into two T-gates. - - .. seealso:: :class:`~.S` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceS.resources() - {T: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The S-gate decomposes into two T-gates. - """ - gate_types = {} - t = ResourceT.resource_rep() - gate_types[t] = 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of the S-gate is equivalent to the S-gate raised to the third power. - The resources are defined as three instances of the S-gate. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 3} - - @staticmethod - def controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires): - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The S-gate is equivalent to the PhaseShift gate for some fixed phase. Given a single - control wire, the cost is therefore a single instance of - :class:`~.ResourceControlledPhaseShift`. Two additional :class:`~.ResourceX` gates are - used to flip the control qubit if it is zero-controlled. - - In the case where multiple controlled wires are provided, we can collapse the control - wires by introducing one 'clean' auxilliary qubit (which gets reset at the end). - In this case the cost increases by two additional :class:`~.ResourceMultiControlledX` gates, - as described in (Lemma 7.11) `Barenco et al. `_. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1: - gate_types = {re.ResourceControlledPhaseShift.resource_rep(): 1} - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 - - return gate_types - - cs = re.ResourceControlledPhaseShift.resource_rep() - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - return {cs: 1, mcx: 2} - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - The S-gate, when raised to a power which is a multiple of four, produces identity. - The cost of raising to an arbitrary integer power :math:`z` is given by - :math:`z \mod 4` instances of the S-gate. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if (mod_4 := z % 4) == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): mod_4} - - -class ResourceSWAP(qml.SWAP, re.ResourceOperator): - r"""Resource class for the SWAP gate. - - Args: - wires (Sequence[int]): the wires the operation acts on - - Resources: - The resources come from the following identity expressing SWAP as the product of - three :class:`~.CNOT` gates: - - .. math:: - - SWAP = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0\\ - 0 & 1 & 0 & 0\\ - 0 & 0 & 0 & 1 - \end{bmatrix} - = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0\\ - 0 & 0 & 0 & 1\\ - 0 & 0 & 1 & 0 - \end{bmatrix} - \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1\\ - 0 & 0 & 1 & 0\\ - 0 & 1 & 0 & 0 - \end{bmatrix} - \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0\\ - 0 & 0 & 0 & 1\\ - 0 & 0 & 1 & 0 - \end{bmatrix}. - - .. seealso:: :class:`~.SWAP` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceSWAP.resources() - {CNOT: 3} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources come from the following identity expressing SWAP as the product of - three CNOT gates: - - .. math:: - - SWAP = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0\\ - 0 & 1 & 0 & 0\\ - 0 & 0 & 0 & 1 - \end{bmatrix} - = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0\\ - 0 & 0 & 0 & 1\\ - 0 & 0 & 1 & 0 - \end{bmatrix} - \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1\\ - 0 & 0 & 1 & 0\\ - 0 & 1 & 0 & 0 - \end{bmatrix} - \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0\\ - 0 & 0 & 0 & 1\\ - 0 & 0 & 1 & 0 - \end{bmatrix}. - """ - gate_types = {} - cnot = re.ResourceCNOT.resource_rep() - gate_types[cnot] = 3 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - For a single control wire, the cost is a single instance of :class:`~.ResourceCSWAP`. - Two additional :class:`~.ResourceX` gates are used to flip the control qubit if - it is zero-controlled. - - In the case where multiple controlled wires are provided, the resources are given by - two :class:`~.ResourceCNOT` gates and one :class:`~.ResourceMultiControlledX` gate. This - is because of the symmetric resource decomposition of the SWAP gate. By controlling on - the middle CNOT gate, we obtain the required controlled operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1: - gate_types = {re.ResourceCSWAP.resource_rep(): 1} - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 - - return gate_types - - cnot = re.ResourceCNOT.resource_rep() - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - return {cnot: 2, mcx: 1} - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - The SWAP gate raised to even powers produces identity and raised - to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if z % 2 == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} - - -class ResourceT(qml.T, re.ResourceOperator): - r"""Resource class for the T-gate. - - Args: - wires (Sequence[int] or int): the wire the operation acts on - - Resources: - The T-gate is treated as a fundamental gate and thus it cannot be decomposed - further. Requesting the resources of this gate raises a :code:`ResourcesNotDefined` error. - - .. seealso:: :class:`~.T` - - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The T-gate is treated as a fundamental gate and thus it cannot be decomposed - further. Requesting the resources of this gate raises a :code:`ResourcesNotDefined` error. - - Raises: - ResourcesNotDefined: This gate is fundamental, no further decomposition defined. - """ - raise re.ResourcesNotDefined - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of the T-gate is equivalent to the T-gate raised to the 7th power. - The resources are defined as seven instances of the T-gate. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - - return {cls.resource_rep(): 7} - - @staticmethod - def controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires): - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The T-gate is equivalent to the PhaseShift gate for some fixed phase. Given a single - control wire, the cost is therefore a single instance of - :class:`~.ResourceControlledPhaseShift`. Two additional :class:`~.ResourceX` gates are - used to flip the control qubit if it is zero-controlled. - - In the case where multiple controlled wires are provided, we can collapse the control - wires by introducing one 'clean' auxilliary qubit (which gets reset at the end). - In this case the cost increases by two additional :class:`~.ResourceMultiControlledX` gates, - as described in (Lemma 7.11) `Barenco et al. `_. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1: - gate_types = {re.ResourceControlledPhaseShift.resource_rep(): 1} - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 - - return gate_types - - ct = re.ResourceControlledPhaseShift.resource_rep() - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - return {ct: 1, mcx: 2} - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - The T-gate, when raised to a power which is a multiple of eight, produces identity. - The cost of raising to an arbitrary integer power :math:`z` is given by - :math:`z \mod 8` instances of the T-gate. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if (mod_8 := z % 8) == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): mod_8} - - -class ResourceX(qml.X, re.ResourceOperator): - r"""Resource class for the X-gate. - - Args: - wires (Sequence[int] or int): the wire the operation acts on - - Resources: - The X-gate can be decomposed according to the following identities: - - .. math:: - - \begin{align} - \hat{X} &= \hat{H} \cdot \hat{Z} \cdot \hat{H}, \\ - \hat{Z} &= \hat{S}^{2}. - \end{align} - - Thus the resources for an X-gate are two :class:`~.ResourceS` gates and - two :class:`~.ResourceHadamard` gates. - - .. seealso:: :class:`~.X` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceX.resources() - {S: 2, Hadamard: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The X-gate can be decomposed according to the following identities: - - .. math:: - - \begin{align} - \hat{X} &= \hat{H} \cdot \hat{Z} \cdot \hat{H}, \\ - \hat{Z} &= \hat{S}^{2}. - \end{align} - - Thus the resources for an X-gate are two :class:`~.ResourceS` gates and - two :class:`~.ResourceHadamard` gates. - """ - s = re.ResourceS.resource_rep() - h = re.ResourceHadamard.resource_rep() - - gate_types = {} - gate_types[s] = 2 - gate_types[h] = 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp(num_ctrl_wires, num_ctrl_values, num_work_wires): - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - For one or two control wires, the cost is one of :class:`~.ResourceCNOT` - or :class:`~.ResourceToffoli` respectively. Two additional :class:`~.ResourceX` gates - per control qubit are used to flip the control qubits if they are zero-controlled. - - In the case where multiple controlled wires are provided, the cost is one general - :class:`~.ResourceMultiControlledX` gate. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires > 2: - return { - re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires, num_ctrl_values, num_work_wires - ): 1 - } - - gate_types = {} - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 * num_ctrl_values - - if num_ctrl_wires == 1: - gate_types[re.ResourceCNOT.resource_rep()] = 1 - - if num_ctrl_wires == 2: - gate_types[re.ResourceToffoli.resource_rep()] = 1 - - return gate_types - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - The X-gate raised to even powers produces identity and raised - to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if z % 2 == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} - - -class ResourceY(qml.Y, re.ResourceOperator): - r"""Resource class for the Y-gate. - - Args: - wires (Sequence[int] or int): the wire the operation acts on - - Resources: - The Y-gate can be decomposed according to the following identities: - - .. math:: - - \begin{align} - \hat{Y} &= \hat{S} \cdot \hat{X} \cdot \hat{S}^{\dagger}, \\ - \hat{X} &= \hat{H} \cdot \hat{Z} \cdot \hat{H}, \\ - \hat{Z} &= \hat{S}^{2}, \\ - \hat{S}^{\dagger} &= 3 \hat{S}. - \end{align} - - Thus the resources for a Y-gate are six :class:`~.ResourceS` gates and - two :class:`~.ResourceHadamard` gates. - - .. seealso:: :class:`~.Y` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceY.resources() - {S: 6, Hadamard: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The Y-gate can be decomposed according to the following identities: - - .. math:: - - \begin{align} - \hat{Y} &= \hat{S} \cdot \hat{X} \cdot \hat{S}^{\dagger}, \\ - \hat{X} &= \hat{H} \cdot \hat{Z} \cdot \hat{H}, \\ - \hat{Z} &= \hat{S}^{2}, \\ - \hat{S}^{\dagger} &= 3 \hat{S}. - \end{align} - - Thus the resources for a Y-gate are six :class:`~.ResourceS` gates and - two :class:`~.ResourceHadamard` gates. - """ - s = re.ResourceS.resource_rep() - h = re.ResourceHadamard.resource_rep() - - gate_types = {} - gate_types[s] = 6 - gate_types[h] = 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - For a single control wire, the cost is a single instance of :class:`~.ResourceCY`. - Two additional :class:`~.ResourceX` gates are used to flip the control qubit if - it is zero-controlled. - - In the case where multiple controlled wires are provided, the resources are derived from - the following identity: - - .. math:: \hat{Y} = \hat{S} \cdot \hat{X} \cdot \hat{S}^{\dagger}. - - Specifically, the resources are given by a :class:`~.ResourceX` gate conjugated with - a pair of :class:`~.ResourceS` gates. By replacing the :class:`~.ResourceX` gate with a - :class:`~.ResourceMultiControlledX` gate, we obtain a controlled-version of this identity. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1: - gate_types = {re.ResourceCY.resource_rep(): 1} - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 - - return gate_types - - gate_types = {} - - s = re.ResourceS.resource_rep() - s_dagg = re.ResourceAdjoint.resource_rep(re.ResourceS, {}) - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - gate_types[s] = 1 - gate_types[s_dagg] = 1 - gate_types[mcx] = 1 - return gate_types - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - The Y-gate raised to even powers produces identity and raised - to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if z % 2 == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} - - -class ResourceZ(qml.Z, re.ResourceOperator): - r"""Resource class for the Z-gate. - - Args: - wires (Sequence[int] or int): the wire the operation acts on - - Resources: - The Z-gate can be decomposed according to the following identities: - - .. math:: \hat{Z} = \hat{S}^{2}, - - thus the resources for a Z-gate are two :class:`~.ResourceS` gates. - - .. seealso:: :class:`~.Z` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceZ.resources() - {S: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The Z-gate can be decomposed according to the following identities: - - .. math:: \hat{Z} = \hat{S}^{2}, - - thus the resources for a Z-gate are two :class:`~.ResourceS` gates. - """ - s = re.ResourceS.resource_rep() - - gate_types = {} - gate_types[s] = 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - This operation is self-adjoint, so the resources of the adjoint operation results - in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - For one or two control wires, the cost is one of :class:`~.ResourceCZ` - or :class:`~.ResourceCCZ` respectively. Two additional :class:`~.ResourceX` gates - per control qubit are used to flip the control qubits if they are zero-controlled. - - In the case where multiple controlled wires are provided, the resources are derived from - the following identity: - - .. math:: \hat{Z} = \hat{H} \cdot \hat{X} \cdot \hat{H}. - - Specifically, the resources are given by a :class:`~.ResourceX` gate conjugated with - a pair of :class:`~.ResourceHadamard` gates. By replacing the :class:`~.ResourceX` gate - with a :class:`~.ResourceMultiControlledX` gate, we obtain a controlled-version of this - identity. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires > 2: - h = re.ResourceHadamard.resource_rep() - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - return {h: 2, mcx: 1} - - gate_types = {} - if num_ctrl_wires == 1: - gate_types[re.ResourceCZ.resource_rep()] = 1 - - if num_ctrl_wires == 2: - gate_types[re.ResourceCCZ.resource_rep()] = 1 - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 * num_ctrl_values - - return gate_types - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - The Z-gate raised to even powers produces identity and raised - to odd powers it produces itself. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if z % 2 == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} diff --git a/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_multi_qubit.py b/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_multi_qubit.py deleted file mode 100644 index b93681aa8f4..00000000000 --- a/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_multi_qubit.py +++ /dev/null @@ -1,1223 +0,0 @@ -# Copyright 2025 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. -r"""Resource operators for parametric multi qubit operations.""" -from typing import Dict - -import pennylane as qml -import pennylane.labs.resource_estimation as re - -# pylint: disable=arguments-differ - - -class ResourceMultiRZ(qml.MultiRZ, re.ResourceOperator): - r"""Resource class for the MultiRZ gate. - - Args: - theta (tensor_like or float): rotation angle :math:`\theta` - wires (Sequence[int] or int): the wires the operation acts on - id (str or None): String representing the operation (optional) - - Resource Parameters: - * num_wires (int): the number of qubits the operation acts upon - - Resources: - The resources come from Section VIII (Figure 3) of `The Bravyi-Kitaev transformation for - quantum computation of electronic structure `_ paper. - - Specifically, the resources are given by one :class:`~.ResourceRZ` gate and a cascade of - :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits - the gate acts on. - - .. seealso:: :class:`~.MultiRZ` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceMultiRZ.resources(num_wires=3) - {CNOT: 4, RZ: 1} - """ - - @staticmethod - def _resource_decomp(num_wires, **kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - num_wires (int): the number of qubits the operation acts upon - - Resources: - The resources come from Section VIII (Figure 3) of `The Bravyi-Kitaev transformation for - quantum computation of electronic structure `_ paper. - - Specifically, the resources are given by one :class:`~.ResourceRZ` gate and a cascade of - :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits - the gate acts on. - """ - cnot = re.ResourceCNOT.resource_rep() - rz = re.ResourceRZ.resource_rep() - - gate_types = {} - gate_types[cnot] = 2 * (num_wires - 1) - gate_types[rz] = 1 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * num_wires (int): the number of qubits the operation acts upon - """ - return {"num_wires": len(self.wires)} - - @classmethod - def resource_rep(cls, num_wires): - """Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - num_wires (int): the number of qubits the operation acts upon - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - return re.CompressedResourceOp(cls, {"num_wires": num_wires}) - - @classmethod - def adjoint_resource_decomp(cls, num_wires) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Args: - num_wires (int): the number of qubits the operation acts upon - - Resources: - The adjoint of this operator just changes the sign of the phase, thus - the resources of the adjoint operation results in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(num_wires=num_wires): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, - num_ctrl_values, - num_work_wires, - num_wires, - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - num_wires (int): the number of qubits the base operation acts upon - - Resources: - The resources are derived from the following identity. If an operation :math:`\hat{A}` - can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}` - then the controlled operation :math:`C\hat{A}` can be expressed as: - - .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger} - - Specifically, the resources are one multi-controlled RZ-gate and a cascade of - :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits - the gate acts on. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_values == 0: - cnot = re.ResourceCNOT.resource_rep() - ctrl_rz = re.ResourceControlled.resource_rep( - base_class=re.ResourceRZ, - base_params={}, - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - gate_types = {} - gate_types[cnot] = 2 * (num_wires - 1) - gate_types[ctrl_rz] = 1 - - return gate_types - - raise re.ResourcesNotDefined - - @classmethod - def pow_resource_decomp(cls, z, num_wires) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - num_wires (int): the number of qubits the base operation acts upon - - Resources: - Taking arbitrary powers of a general rotation produces a sum of rotations. - The resources simplify to just one total multi-RZ rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(num_wires=num_wires): 1} - - -class ResourcePauliRot(qml.PauliRot, re.ResourceOperator): - r"""Resource class for the PauliRot gate. - - Args: - theta (float): rotation angle :math:`\theta` - pauli_word (string): the Pauli word defining the rotation - wires (Sequence[int] or int): the wire the operation acts on - id (str or None): String representing the operation (optional) - - Resource Parameters: - * pauli_string (str): a string describing the pauli operators that define the rotation - - Resources: - When the :code:`pauli_string` is a single Pauli operator (:code:`X, Y, Z, Identity`) - the cost is the associated single qubit rotation (:code:`RX, RY, RZ, GlobalPhase`). - - The resources come from Section VIII (Figures 3 & 4) of `The Bravyi-Kitaev transformation - for quantum computation of electronic structure `_ paper, - in combination with the following identity: - - .. math:: - - \begin{align} - \hat{X} &= \hat{H} \cdot \hat{Z} \cdot \hat{H}, \\ - \hat{Y} &= \hat{S} \cdot \hat{H} \cdot \hat{Z} \cdot \hat{H} \cdot \hat{S}^{\dagger}. - \end{align} - - Specifically, the resources are given by one :class:`~.ResourceRZ` gate and a cascade of - :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits - the gate acts on. Additionally, for each :code:`X` gate in the Pauli word we conjugate by - a pair of :class:`~.ResourceHadamard` gates, and for each :code:`Y` gate in the Pauli word we - conjugate by a pair of :class:`~.ResourceHadamard` and a pair of :class:`~.ResourceS` gates. - - .. seealso:: :class:`~.PauliRot` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourcePauliRot.resources(pauli_string="XYZ") - {Hadamard: 4, S: 1, Adjoint(S): 1, RZ: 1, CNOT: 4} - """ - - @staticmethod - def _resource_decomp(pauli_string, **kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - pauli_string (str): a string describing the pauli operators that define the rotation - - Resources: - When the :code:`pauli_string` is a single Pauli operator (:code:`X, Y, Z, Identity`) - the cost is the associated single qubit rotation (:code:`RX, RY, RZ, GlobalPhase`). - - The resources come from Section VIII (Figures 3 & 4) of `The Bravyi-Kitaev transformation - for quantum computation of electronic structure `_ paper, - in combination with the following identity: - - .. math:: - - \begin{align} - \hat{X} &= \hat{H} \cdot \hat{Z} \cdot \hat{H}, \\ - \hat{Y} &= \hat{S} \cdot \hat{H} \cdot \hat{Z} \cdot \hat{H} \cdot \hat{S}^{\dagger}. - \end{align} - - Specifically, the resources are given by one :class:`~.ResourceRZ` gate and a cascade of - :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits - the gate acts on. Additionally, for each :code:`X` gate in the Pauli word we conjugate by - a pair of :class:`~.ResourceHadamard` gates, and for each :code:`Y` gate in the Pauli word we - conjugate by a pair of :class:`~.ResourceHadamard` and a pair of :class:`~.ResourceS` gates. - """ - if (set(pauli_string) == {"I"}) or (len(pauli_string) == 0): - gp = re.ResourceGlobalPhase.resource_rep() - return {gp: 1} - - if pauli_string == "X": - return {re.ResourceRX.resource_rep(): 1} - if pauli_string == "Y": - return {re.ResourceRY.resource_rep(): 1} - if pauli_string == "Z": - return {re.ResourceRZ.resource_rep(): 1} - - active_wires = len(pauli_string.replace("I", "")) - - h = re.ResourceHadamard.resource_rep() - s = re.ResourceS.resource_rep() - rz = re.ResourceRZ.resource_rep() - s_dagg = re.ResourceAdjoint.resource_rep(re.ResourceS, {}) - cnot = re.ResourceCNOT.resource_rep() - - h_count = 0 - s_count = 0 - - for gate in pauli_string: - if gate == "X": - h_count += 1 - if gate == "Y": - h_count += 1 - s_count += 1 - - gate_types = {} - if h_count: - gate_types[h] = 2 * h_count - - if s_count: - gate_types[s] = s_count - gate_types[s_dagg] = s_count - - gate_types[rz] = 1 - gate_types[cnot] = 2 * (active_wires - 1) - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * pauli_string (str): a string describing the pauli operators that define the rotation - """ - return { - "pauli_string": self.hyperparameters["pauli_word"], - } - - @classmethod - def resource_rep(cls, pauli_string): - """Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - pauli_string (str): a string describing the pauli operators that define the rotation - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - return re.CompressedResourceOp(cls, {"pauli_string": pauli_string}) - - @classmethod - def adjoint_resource_decomp(cls, pauli_string) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Args: - pauli_string (str): a string describing the pauli operators that define the rotation - - Resources: - The adjoint of this operator just changes the sign of the phase, thus - the resources of the adjoint operation results in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(pauli_string=pauli_string): 1} - - @classmethod - def controlled_resource_decomp( - cls, - num_ctrl_wires, - num_ctrl_values, - num_work_wires, - pauli_string, - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - pauli_string (str): a string describing the pauli operators that define the rotation - - Resources: - When the :code:`pauli_string` is a single Pauli operator (:code:`X, Y, Z, Identity`) - the cost is the associated controlled single qubit rotation gate: (:class:`~.ResourceCRX`, - :class:`~.ResourceCRY`, :class:`~.ResourceCRZ`, controlled-:class:`~.ResourceGlobalPhase`). - - The resources are derived from the following identity. If an operation :math:`\hat{A}` - can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}` - then the controlled operation :math:`C\hat{A}` can be expressed as: - - .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger} - - Specifically, the resources are one multi-controlled RZ-gate and a cascade of - :math:`2 * (n - 1)` :class:`~.ResourceCNOT` gates where :math:`n` is the number of qubits - the gate acts on. Additionally, for each :code:`X` gate in the Pauli word we conjugate by - a pair of :class:`~.ResourceHadamard` gates, and for each :code:`Y` gate in the Pauli word - we conjugate by a pair of :class:`~.ResourceHadamard` and a pair of :class:`~.ResourceS` gates. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - base_gate_types = cls.resources(pauli_string) - - pivotal_gates = ( - re.ResourceRX.resource_rep(), - re.ResourceRY.resource_rep(), - re.ResourceRZ.resource_rep(), - re.ResourceGlobalPhase.resource_rep(), - ) - - for gate in pivotal_gates: - if gate in base_gate_types: - counts = base_gate_types.pop(gate) - ctrl_gate = re.ResourceControlled.resource_rep( - gate.op_type, - gate.params, - num_ctrl_wires, - num_ctrl_values, - num_work_wires, - ) - - base_gate_types[ctrl_gate] = counts - - return base_gate_types - - @classmethod - def pow_resource_decomp(cls, z, pauli_string) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - pauli_string (str): a string describing the pauli operators that define the rotation - - Resources: - Taking arbitrary powers of a general rotation produces a sum of rotations. - The resources simplify to just one total pauli rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(pauli_string=pauli_string): 1} - - -class ResourceIsingXX(qml.IsingXX, re.ResourceOperator): - r"""Resource class for the IsingXX gate. - - Args: - phi (float): the phase angle - wires (int): the subsystem the gate acts on - id (str or None): String representing the operation (optional) - - Resources: - Ising XX coupling gate - - .. math:: XX(\phi) = \exp\left(-i \frac{\phi}{2} (X \otimes X)\right) = - \begin{bmatrix} = - \cos(\phi / 2) & 0 & 0 & -i \sin(\phi / 2) \\ - 0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\ - 0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\ - -i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2) - \end{bmatrix}. - - The circuit implementing this transformation is given by: - - .. code-block:: bash - - 0: ─╭●─────RX────╭●─┤ - 1: ─╰X───────────╰X─┤ - - .. seealso:: :class:`~.IsingXX` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceIsingXX.resources() - {CNOT: 2, RX: 1} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - Ising XX coupling gate - - .. math:: XX(\phi) = \exp\left(-i \frac{\phi}{2} (X \otimes X)\right) = - \begin{bmatrix} = - \cos(\phi / 2) & 0 & 0 & -i \sin(\phi / 2) \\ - 0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\ - 0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\ - -i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2) - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ─╭●─────RX────╭●─┤ - 1: ─╰X───────────╰X─┤ - - """ - cnot = re.ResourceCNOT.resource_rep() - rx = re.ResourceRX.resource_rep() - - gate_types = {} - gate_types[cnot] = 2 - gate_types[rx] = 1 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of this operator just changes the sign of the phase angle, thus - the resources of the adjoint operation results in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, - num_ctrl_values, - num_work_wires, - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are derived from the following identity. If an operation :math:`\hat{A}` - can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}` - then the controlled operation :math:`C\hat{A}` can be expressed as: - - .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger} - - Specifically, the resources are one multi-controlled RX-gate and a pair of - :class:`~.ResourceCNOT` gates. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_values == 0: - cnot = re.ResourceCNOT.resource_rep() - ctrl_rx = re.ResourceControlled.resource_rep( - base_class=re.ResourceRX, - base_params={}, - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - gate_types = {} - gate_types[cnot] = 2 - gate_types[ctrl_rx] = 1 - - return gate_types - raise re.ResourcesNotDefined - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a rotation produces a sum of rotations. - The resources simplify to just one total Ising rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - -class ResourceIsingYY(qml.IsingYY, re.ResourceOperator): - r"""Resource class for the IsingYY gate. - - Args: - phi (float): the phase angle - wires (int): the subsystem the gate acts on - id (str or None): String representing the operation (optional) - - Resources: - Ising YY coupling gate - - .. math:: \mathtt{YY}(\phi) = \exp\left(-i \frac{\phi}{2} (Y \otimes Y)\right) = - \begin{bmatrix} - \cos(\phi / 2) & 0 & 0 & i \sin(\phi / 2) \\ - 0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\ - 0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\ - i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2) - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ─╭●─────RY────╭●─┤ - 1: ─╰Y───────────╰Y─┤ - - .. seealso:: :class:`~.IsingYY` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceIsingYY.resources() - {CY: 2, RY: 1} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - Ising YY coupling gate - - .. math:: \mathtt{YY}(\phi) = \exp\left(-i \frac{\phi}{2} (Y \otimes Y)\right) = - \begin{bmatrix} - \cos(\phi / 2) & 0 & 0 & i \sin(\phi / 2) \\ - 0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\ - 0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\ - i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2) - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ─╭●─────RY────╭●─┤ - 1: ─╰Y───────────╰Y─┤ - - """ - - cy = re.ops.ResourceCY.resource_rep() - ry = re.ops.ResourceRY.resource_rep() - - gate_types = {} - gate_types[cy] = 2 - gate_types[ry] = 1 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of this operator just changes the sign of the phase angle, thus - the resources of the adjoint operation results in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, - num_ctrl_values, - num_work_wires, - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are derived from the following identity. If an operation :math:`\hat{A}` - can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}` - then the controlled operation :math:`C\hat{A}` can be expressed as: - - .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger} - - Specifically, the resources are one multi-controlled RY-gate and a pair of - :class:`~.ResourceCY` gates. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_values == 0: - cy = re.ops.ResourceCY.resource_rep() - ctrl_ry = re.ResourceControlled.resource_rep( - base_class=re.ResourceRY, - base_params={}, - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - gate_types = {} - gate_types[cy] = 2 - gate_types[ctrl_ry] = 1 - - return gate_types - raise re.ResourcesNotDefined - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a rotation produces a sum of rotations. - The resources simplify to just one total Ising rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - -class ResourceIsingXY(qml.IsingXY, re.ResourceOperator): - r"""Resource class for the IsingXY gate. - - Args: - phi (float): the phase angle - wires (int): the subsystem the gate acts on - id (str or None): String representing the operation (optional) - - Resources: - Ising (XX + YY) coupling gate - - .. math:: \mathtt{XY}(\phi) = \exp\left(i \frac{\theta}{4} (X \otimes X + Y \otimes Y)\right) = - \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & \cos(\phi / 2) & i \sin(\phi / 2) & 0 \\ - 0 & i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ──H─╭●─────RY────╭●──H─┤ - 1: ────╰Y─────RX────╰Y────┤ - - .. seealso:: :class:`~.IsingXY` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceIsingXY.resources() - {Hadamard: 2, CY: 2, RY: 1, RX: 1} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - IsingXY coupling gate - - .. math:: \mathtt{XY}(\phi) = \exp\left(i \frac{\theta}{4} (X \otimes X + Y \otimes Y)\right) = - \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & \cos(\phi / 2) & i \sin(\phi / 2) & 0 \\ - 0 & i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ──H─╭●─────RY────╭●──H─┤ - 1: ────╰Y─────RX────╰Y────┤ - - """ - h = re.ResourceHadamard.resource_rep() - cy = re.ResourceCY.resource_rep() - ry = re.ResourceRY.resource_rep() - rx = re.ResourceRX.resource_rep() - - gate_types = {} - gate_types[h] = 2 - gate_types[cy] = 2 - gate_types[ry] = 1 - gate_types[rx] = 1 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of this operator just changes the sign of the phase angle, thus - the resources of the adjoint operation results in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, - num_ctrl_values, - num_work_wires, - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are derived from the following identity. If an operation :math:`\hat{A}` - can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}` - then the controlled operation :math:`C\hat{A}` can be expressed as: - - .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger} - - Specifically, the resources are one multi-controlled RY-gate, one multi-controlled RX-gate, - a pair of :class:`~.ResourceCY` gates and a pair of :class:`~.ResourceHadamard` gates. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_values == 0: - h = re.ResourceHadamard.resource_rep() - cy = re.ResourceCY.resource_rep() - ctrl_rx = re.ResourceControlled.resource_rep( - base_class=re.ResourceRX, - base_params={}, - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - ctrl_ry = re.ResourceControlled.resource_rep( - base_class=re.ResourceRY, - base_params={}, - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - gate_types = {} - gate_types[h] = 2 - gate_types[cy] = 2 - gate_types[ctrl_ry] = 1 - gate_types[ctrl_rx] = 1 - - return gate_types - raise re.ResourcesNotDefined - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a rotation produces a sum of rotations. - The resources simplify to just one total Ising rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - -class ResourceIsingZZ(qml.IsingZZ, re.ResourceOperator): - r"""Resource class for the IsingZZ gate. - - Args: - phi (float): the phase angle - wires (int): the subsystem the gate acts on - id (str or None): String representing the operation (optional) - - Resources: - Ising ZZ coupling gate - - .. math:: ZZ(\phi) = \exp\left(-i \frac{\phi}{2} (Z \otimes Z)\right) = - \begin{bmatrix} - e^{-i \phi / 2} & 0 & 0 & 0 \\ - 0 & e^{i \phi / 2} & 0 & 0 \\ - 0 & 0 & e^{i \phi / 2} & 0 \\ - 0 & 0 & 0 & e^{-i \phi / 2} - \end{bmatrix}. - - The cost for implmenting this transformation is given by: - - .. code-block:: bash - - 0: ─╭●───────────╭●─┤ - 1: ─╰X─────RZ────╰X─┤ - - .. seealso:: :class:`~.IsingZZ` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceIsingZZ.resources() - {CNOT: 2, RZ: 1} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - Ising ZZ coupling gate - - .. math:: ZZ(\phi) = \exp\left(-i \frac{\phi}{2} (Z \otimes Z)\right) = - \begin{bmatrix} - e^{-i \phi / 2} & 0 & 0 & 0 \\ - 0 & e^{i \phi / 2} & 0 & 0 \\ - 0 & 0 & e^{i \phi / 2} & 0 \\ - 0 & 0 & 0 & e^{-i \phi / 2} - \end{bmatrix}. - - The cost for implmenting this transformation is given by: - - .. code-block:: bash - - 0: ─╭●───────────╭●─┤ - 1: ─╰X─────RZ────╰X─┤ - - """ - cnot = re.ResourceCNOT.resource_rep() - rz = re.ResourceRZ.resource_rep() - - gate_types = {} - gate_types[cnot] = 2 - gate_types[rz] = 1 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of this operator just changes the sign of the phase angle, thus - the resources of the adjoint operation results in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, - num_ctrl_values, - num_work_wires, - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are derived from the following identity. If an operation :math:`\hat{A}` - can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}` - then the controlled operation :math:`C\hat{A}` can be expressed as: - - .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger} - - Specifically, the resources are one multi-controlled RZ-gate and a pair of - :class:`~.ResourceCNOT` gates. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_values == 0: - cnot = re.ResourceCNOT.resource_rep() - ctrl_rz = re.ResourceControlled.resource_rep( - base_class=re.ResourceRZ, - base_params={}, - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - gate_types = {} - gate_types[cnot] = 2 - gate_types[ctrl_rz] = 1 - - return gate_types - raise re.ResourcesNotDefined - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a rotation produces a sum of rotations. - The resources simplify to just one total Ising rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - -class ResourcePSWAP(qml.PSWAP, re.ResourceOperator): - r"""Resource class for the PSWAP gate. - - Args: - phi (float): the phase angle - wires (int): the subsystem the gate acts on - id (str or None): String representing the operation (optional) - - Resources: - The :code:`PSWAP` gate is defined as: - - .. math:: PSWAP(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & e^{i \phi} & 0 \\ - 0 & e^{i \phi} & 0 & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ─╭SWAP─╭●───────────╭●─┤ - 1: ─╰SWAP─╰X─────Rϕ────╰X─┤ - - .. seealso:: :class:`~.PSWAP` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourcePSWAP.resources() - {SWAP: 1, CNOT: 2, PhaseShift: 1} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The :code:`PSWAP` gate is defined as: - - .. math:: PSWAP(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 0 & e^{i \phi} & 0 \\ - 0 & e^{i \phi} & 0 & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ─╭SWAP─╭●───────────╭●─┤ - 1: ─╰SWAP─╰X─────Rϕ────╰X─┤ - - """ - swap = re.ResourceSWAP.resource_rep() - cnot = re.ResourceCNOT.resource_rep() - phase = re.ResourcePhaseShift.resource_rep() - - gate_types = {} - gate_types[swap] = 1 - gate_types[cnot] = 2 - gate_types[phase] = 1 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of this operator just changes the sign of the phase angle, thus - the resources of the adjoint operation results in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, - num_ctrl_values, - num_work_wires, - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - The resources are derived from the following identity. If an operation :math:`\hat{A}` - can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}` - then the controlled operation :math:`C\hat{A}` can be expressed as: - - .. math:: C\hat{A} \ = \ \hat{U} \cdot C\hat{B} \cdot \hat{U}^{\dagger} - - Specifically, the resources are one multi-controlled phase shift gate, one multi-controlled - SWAP gate and a pair of :class:`~.ResourceCNOT` gates. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_values == 0: - cnot = re.ResourceCNOT.resource_rep() - ctrl_swap = re.ResourceControlled.resource_rep( - base_class=re.ResourceSWAP, - base_params={}, - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - ctrl_ps = re.ResourceControlled.resource_rep( - base_class=re.ResourcePhaseShift, - base_params={}, - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - gate_types = {} - gate_types[ctrl_swap] = 1 - gate_types[cnot] = 2 - gate_types[ctrl_ps] = 1 - return gate_types - - raise re.ResourcesNotDefined diff --git a/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_single_qubit.py b/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_single_qubit.py deleted file mode 100644 index 9c8c503942d..00000000000 --- a/pennylane/labs/resource_estimation/ops/qubit/parametric_ops_single_qubit.py +++ /dev/null @@ -1,795 +0,0 @@ -# Copyright 2025 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. -r"""Resource operators for parametric single qubit operations.""" -from typing import Dict - -import numpy as np - -import pennylane as qml -import pennylane.labs.resource_estimation as re - -# pylint: disable=arguments-differ - - -def _rotation_resources(epsilon=10e-3): - r"""An estimate on the number of T gates needed to implement a Pauli rotation. - - The expected T-count is taken from (the 'Simulation Results' section) `Efficient - Synthesis of Universal Repeat-Until-Success Circuits `_. - The cost is given as: - - .. math:: T_{count} = \lceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)\rceil - - Args: - epsilon (float): the acceptable error threshold for the approximation - - Returns: - dict: the T-gate counts - - """ - gate_types = {} - - num_gates = round(1.149 * np.log2(1 / epsilon) + 9.2) - t = re.ResourceT.resource_rep() - gate_types[t] = num_gates - - return gate_types - - -class ResourcePhaseShift(qml.PhaseShift, re.ResourceOperator): - r"""Resource class for the PhaseShift gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int] or int): the wire the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The phase shift gate is equivalent to a Z-rotation upto some global phase, - as defined from the following identity: - - .. math:: R_\phi(\phi) = e^{i\phi/2}R_z(\phi) = \begin{bmatrix} - 1 & 0 \\ - 0 & e^{i\phi} - \end{bmatrix}. - - .. seealso:: :class:`~.PhaseShift` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourcePhaseShift.resources() - {RZ: 1, GlobalPhase: 1} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The phase shift gate is equivalent to a Z-rotation upto some global phase, - as defined from the following identity: - - .. math:: R_\phi(\phi) = e^{i\phi/2}R_z(\phi) = \begin{bmatrix} - 1 & 0 \\ - 0 & e^{i\phi} - \end{bmatrix}. - """ - gate_types = {} - rz = re.ResourceRZ.resource_rep() - global_phase = re.ResourceGlobalPhase.resource_rep() - gate_types[rz] = 1 - gate_types[global_phase] = 1 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of a phase shift operator just changes the sign of the phase, thus - the resources of the adjoint operation results in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - For a single control wire, the cost is a single instance of - :class:`~.ResourceControlledPhaseShift`. Two additional :class:`~.ResourceX` gates are used - to flip the control qubit if it is zero-controlled. - - In the case where multiple controlled wires are provided, we can collapse the control - wires by introducing one 'clean' auxilliary qubit (which gets reset at the end). - In this case the cost increases by two additional :class:`~.ResourceMultiControlledX` gates, - as described in (lemma 7.11) `Elementary gates for quantum computation `_. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1: - gate_types = {re.ResourceControlledPhaseShift.resource_rep(): 1} - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 - - return gate_types - - c_ps = re.ResourceControlledPhaseShift.resource_rep() - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - return {c_ps: 1, mcx: 2} - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a phase shift produces a sum of shifts. - The resources simplify to just one total phase shift operator. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if z == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} - - -class ResourceRX(qml.RX, re.ResourceOperator): - r"""Resource class for the RX gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int] or int): the wire the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The - resources are approximating the gate with a series of T gates. The expected T-count is taken - from (the 'Simulation Results' section) `Efficient Synthesis of Universal Repeat-Until-Success - Circuits `_. The cost is given as: - - .. math:: T_{count} = \lceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)\rceil - - .. seealso:: :class:`~.RX` - - **Example** - - The resources for this operation are computed using: - - >>> re.get_resources(re.ResourceRX(1.23, 0)) - {'gate_types': defaultdict(, {'T': 17}), 'num_gates': 17, 'num_wires': 1} - - The operation does not require any parameters directly, however, it will depend on the single - qubit error threshold, which can be set using a config dictionary. - - >>> config = {"error_rx": 1e-3} - >>> re.get_resources(re.ResourceRX(1.23, 0), config=config) - {'gate_types': defaultdict(, {'T': 21}), 'num_gates': 21, 'num_wires': 1} - """ - - @staticmethod - def _resource_decomp(config, **kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - config (dict): a dictionary containing the error threshold - - Resources: - A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The - resources are approximating the gate with a series of T gates. The expected T-count is taken - from (the 'Simulation Results' section) `Efficient Synthesis of Universal Repeat-Until-Success - Circuits `_. The cost is given as: - - .. math:: T_{count} = \lceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)\rceil - - """ - return _rotation_resources(epsilon=config["error_rx"]) - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of a single qubit rotation changes the sign of the rotation angle, - thus the resources of the adjoint operation result in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - For a single control wire, the cost is a single instance of :class:`~.ResourceCRX`. - Two additional :class:`~.ResourceX` gates are used to flip the control qubit if - it is zero-controlled. - - In the case where multiple controlled wires are provided, the resources are taken - from Figure 1b of the paper `T-count and T-depth of any multi-qubit unitary - `_. In combination with the following identity: - - .. math:: \hat{RX} = \hat{H} \cdot \hat{RZ} \cdot \hat{H}, - - we can express the :code:`CRX` gate as a :code:`CRZ` gate conjugated by :code:`Hadamard` - gates. The expression for controlled-RZ gates is used as defined in the reference above. - By replacing the :code:`X` gates with multi-controlled :code:`X` gates, we obtain a - controlled-version of that identity. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1: - gate_types = {re.ResourceCRX.resource_rep(): 1} - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 - - return gate_types - - gate_types = {} - - h = re.ResourceHadamard.resource_rep() - rz = re.ResourceRZ.resource_rep() - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - gate_types[mcx] = 2 - gate_types[rz] = 2 - gate_types[h] = 2 - - return gate_types - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a single qubit rotation produces a sum of rotations. - The resources simplify to just one total single qubit rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if z == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} - - -class ResourceRY(qml.RY, re.ResourceOperator): - r"""Resource class for the RY gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int] or int): the wire the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The - resources are approximating the gate with a series of T gates. The expected T-count is taken - from (the 'Simulation Results' section) `Efficient Synthesis of Universal Repeat-Until-Success - Circuits `_. The cost is given as: - - .. math:: T_{count} = \lceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)\rceil - - .. seealso:: :class:`~.RY` - - **Example** - - The resources for this operation are computed using: - - >>> re.get_resources(re.ResourceRY(1.23, 0)) - {'gate_types': defaultdict(, {'T': 17}), 'num_gates': 17, 'num_wires': 1} - - The operation does not require any parameters directly, however, it will depend on the single - qubit error threshold, which can be set using a config dictionary. - - >>> config = {"error_ry": 1e-3} - >>> re.get_resources(re.ResourceRY(1.23, 0), config=config) - {'gate_types': defaultdict(, {'T': 21}), 'num_gates': 21, 'num_wires': 1} - """ - - @staticmethod - def _resource_decomp(config, **kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The - resources are approximating the gate with a series of T gates. The expected T-count is taken - from (the 'Simulation Results' section) `Efficient Synthesis of Universal Repeat-Until-Success - Circuits `_. The cost is given as: - - .. math:: T_{count} = \lceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)\rceil - - Args: - config (dict): a dictionary containing the error threshold - """ - return _rotation_resources(epsilon=config["error_ry"]) - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of a single qubit rotation changes the sign of the rotation angle, - thus the resources of the adjoint operation result in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - For a single control wire, the cost is a single instance of :class:`~.ResourceCRY`. - Two additional :class:`~.ResourceX` gates are used to flip the control qubit if - it is zero-controlled. - - In the case where multiple controlled wires are provided, the resources are taken - from Figure 1b of the paper `T-count and T-depth of any multi-qubit - unitary `_. The resources are derived with the - following identity: - - .. math:: \hat{RY}(\theta) = \hat{X} \cdot \hat{RY}(- \theta) \cdot \hat{X}. - - By replacing the :code:`X` gates with multi-controlled :code:`X` gates, we obtain a - controlled-version of this identity. Thus we are able to constructively or destructively - interfere the gates based on the value of the control qubits. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1: - gate_types = {re.ResourceCRY.resource_rep(): 1} - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 - - return gate_types - - ry = re.ResourceRY.resource_rep() - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - return {ry: 2, mcx: 2} - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a single qubit rotation produces a sum of rotations. - The resources simplify to just one total single qubit rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if z == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} - - -class ResourceRZ(qml.RZ, re.ResourceOperator): - r"""Resource class for the RZ gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int] or int): the wire the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The - resources are approximating the gate with a series of T gates. The expected T-count is taken - from (the 'Simulation Results' section) `Efficient Synthesis of Universal Repeat-Until-Success - Circuits `_. The cost is given as: - - .. math:: T_{count} = \lceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)\rceil - - .. seealso:: :class:`~.RZ` - - **Example** - - The resources for this operation are computed using: - - >>> re.get_resources(re.ResourceRZ(1.23, 0)) - {'gate_types': defaultdict(, {'T': 17}), 'num_gates': 17, 'num_wires': 1} - - The operation does not require any parameters directly, however, it will depend on the single - qubit error threshold, which can be set using a config dictionary. - - >>> config = {"error_rz": 1e-3} - >>> re.get_resources(re.ResourceRZ(1.23, 0), config=config) - {'gate_types': defaultdict(, {'T': 21}), 'num_gates': 21, 'num_wires': 1}s - """ - - @staticmethod - def _resource_decomp(config, **kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - A single qubit rotation gate can be approximately synthesised from Clifford and T gates. The - resources are approximating the gate with a series of T gates. The expected T-count is taken - from (the 'Simulation Results' section) `Efficient Synthesis of Universal Repeat-Until-Success - Circuits `_. The cost is given as: - - .. math:: T_{count} = \lceil(1.149 * log_{2}(\frac{1}{\epsilon}) + 9.2)\rceil - - Args: - config (dict): a dictionary containing the error threshold - """ - return _rotation_resources(epsilon=config["error_rz"]) - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of a single qubit rotation changes the sign of the rotation angle, - thus the resources of the adjoint operation result in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - For a single control wire, the cost is a single instance of :class:`~.ResourceCRY`. - Two additional :class:`~.ResourceX` gates are used to flip the control qubit if - it is zero-controlled. - - In the case where multiple controlled wires are provided, the resources are obtained - from Figure 1b of the paper `T-count and T-depth of any multi-qubit unitary - `_. They are derived from the following identity: - - .. math:: \hat{RZ}(\theta) = \hat{X} \cdot \hat{RZ}(- \theta) \cdot \hat{X}. - - By replacing the :code:`X` gates with multi-controlled :code:`X` gates, we obtain a - controlled-version of this identity. Thus we are able to constructively or destructively - interfere the gates based on the value of the control qubits. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1: - gate_types = {re.ResourceCRZ.resource_rep(): 1} - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 - - return gate_types - - rz = re.ResourceRZ.resource_rep() - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - return {rz: 2, mcx: 2} - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a single qubit rotation produces a sum of rotations. - The resources simplify to just one total single qubit rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if z == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} - - -class ResourceRot(qml.Rot, re.ResourceOperator): - r"""Resource class for the Rot-gate. - - Args: - phi (float): rotation angle :math:`\phi` - theta (float): rotation angle :math:`\theta` - omega (float): rotation angle :math:`\omega` - wires (Any, Wires): the wire the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are obtained according to the definition of the :class:`Rot` gate: - - .. math:: \hat{R}(\omega, \theta, \phi) = \hat{RZ}(\omega) \cdot \hat{RY}(\theta) \cdot \hat{RZ}(\phi). - - .. seealso:: :class:`~.Rot` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceRot.resources() - {RY: 1, RZ: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are obtained according to the definition of the :class:`Rot` gate: - - .. math:: \hat{R}(\omega, \theta, \phi) = \hat{RZ}(\omega) \cdot \hat{RY}(\theta) \cdot \hat{RZ}(\phi). - - """ - ry = ResourceRY.resource_rep() - rz = ResourceRZ.resource_rep() - - gate_types = {ry: 1, rz: 2} - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - @classmethod - def adjoint_resource_decomp(cls) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Resources: - The adjoint of a general single qubit rotation changes the sign of the rotation angles, - thus the resources of the adjoint operation results in the original operation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - return {cls.resource_rep(): 1} - - @staticmethod - def controlled_resource_decomp( - num_ctrl_wires, num_ctrl_values, num_work_wires - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Resources: - For a single control wire, the cost is a single instance of :class:`~.ResourceCRot`. - Two additional :class:`~.ResourceX` gates are used to flip the control qubit if - it is zero-controlled. - - In the case where multiple controlled wires are provided, the resources are derived - from Figure 1b of the paper `T-count and T-depth of any multi-qubit unitary - `_. The resources are derived with the following - identities: - - .. math:: - - \begin{align} - \hat{RZ}(\theta) = \hat{X} \cdot \hat{RZ}(- \theta) \cdot \hat{X}, \\ - \hat{RY}(\theta) = \hat{X} \cdot \hat{RY}(- \theta) \cdot \hat{X}. - \end{align} - - This identity is applied along with some clever choices for the angle values to combine - rotations; the final circuit takes the form: - - .. code-block:: bash - - ctrl: ─────╭●─────────╭●─────────┤ - trgt: ──RZ─╰X──RZ──RY─╰X──RY──RZ─┤ - - The :code:`CNOT` gates are replaced with multi-controlled X-gates to generalize to the - multi-controlled case. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if num_ctrl_wires == 1: - gate_types = {re.ResourceCRot.resource_rep(): 1} - - if num_ctrl_values: - gate_types[re.ResourceX.resource_rep()] = 2 - - return gate_types - - gate_types = {} - - rz = re.ResourceRZ.resource_rep() - ry = re.ResourceRY.resource_rep() - mcx = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=num_ctrl_wires, - num_ctrl_values=num_ctrl_values, - num_work_wires=num_work_wires, - ) - - gate_types[mcx] = 2 - gate_types[rz] = 3 - gate_types[ry] = 2 - - return gate_types - - @classmethod - def pow_resource_decomp(cls, z) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Resources: - Taking arbitrary powers of a general single qubit rotation produces a sum of rotations. - The resources simplify to just one total single qubit rotation. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - if z == 0: - return {re.ResourceIdentity.resource_rep(): 1} - return {cls.resource_rep(): 1} diff --git a/pennylane/labs/resource_estimation/ops/qubit/qchem_ops.py b/pennylane/labs/resource_estimation/ops/qubit/qchem_ops.py deleted file mode 100644 index 58605336caa..00000000000 --- a/pennylane/labs/resource_estimation/ops/qubit/qchem_ops.py +++ /dev/null @@ -1,689 +0,0 @@ -# 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. -r"""Resource operators for qchem operations.""" -import pennylane as qml -import pennylane.labs.resource_estimation as re - -# pylint: disable=arguments-differ - - -class ResourceSingleExcitation(qml.SingleExcitation, re.ResourceOperator): - r"""Resource class for the SingleExcitation gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int]): the wires the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are obtained by decomposing the following matrix into fundamental gates. - - .. math:: U(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & \cos(\phi/2) & -\sin(\phi/2) & 0 \\ - 0 & \sin(\phi/2) & \cos(\phi/2) & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ──T†──H───S─╭X──RZ-─╭X──S†──H──T─┤ - 1: ──T†──S†──H─╰●──RY──╰●──H───S──T─┤ - - .. seealso:: :class:`~.SingleExcitation` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceSingleExcitation.resources() - {Adjoint(T): 2, Hadamard: 4, S: 2, Adjoint(S): 2, CNOT: 2, RZ: 1, RY: 1, T: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are obtained by decomposing the following matrix into fundamental gates. - - .. math:: U(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & \cos(\phi/2) & -\sin(\phi/2) & 0 \\ - 0 & \sin(\phi/2) & \cos(\phi/2) & 0 \\ - 0 & 0 & 0 & 1 - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ──T†──H───S─╭X──RZ-─╭X──S†──H──T─┤ - 1: ──T†──S†──H─╰●──RY──╰●──H───S──T─┤ - - """ - t_dag = re.ResourceAdjoint.resource_rep(re.ResourceT, {}) - h = re.ResourceHadamard.resource_rep() - s = re.ResourceS.resource_rep() - s_dag = re.ResourceAdjoint.resource_rep(re.ResourceS, {}) - cnot = re.ResourceCNOT.resource_rep() - rz = re.ResourceRZ.resource_rep() - ry = re.ResourceRY.resource_rep() - t = re.ResourceT.resource_rep() - - gate_types = {} - gate_types[t_dag] = 2 - gate_types[h] = 4 - gate_types[s] = 2 - gate_types[s_dag] = 2 - gate_types[cnot] = 2 - gate_types[rz] = 1 - gate_types[ry] = 1 - gate_types[t] = 2 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - -class ResourceSingleExcitationMinus(qml.SingleExcitationMinus, re.ResourceOperator): - r"""Resource class for the SingleExcitationMinus gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int] or int): the wires the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are obtained by decomposing the following matrix into fundamental gates. - - .. math:: U_-(\phi) = \begin{bmatrix} - e^{-i\phi/2} & 0 & 0 & 0 \\ - 0 & \cos(\phi/2) & -\sin(\phi/2) & 0 \\ - 0 & \sin(\phi/2) & \cos(\phi/2) & 0 \\ - 0 & 0 & 0 & e^{-i\phi/2} - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ──X─╭Rϕ────X─╭●────╭●─╭RY───╭●─┤ - 1: ──X─╰●─────X─╰Rϕ───╰X─╰●────╰X─┤ - - .. seealso:: :class:`~.SingleExcitationMinus` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceSingleExcitationMinus.resources() - {X: 4, ControlledPhaseShift: 2, CNOT: 2, CRY: 1} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are obtained by decomposing the following matrix into fundamental gates. - - .. math:: U_-(\phi) = \begin{bmatrix} - e^{-i\phi/2} & 0 & 0 & 0 \\ - 0 & \cos(\phi/2) & -\sin(\phi/2) & 0 \\ - 0 & \sin(\phi/2) & \cos(\phi/2) & 0 \\ - 0 & 0 & 0 & e^{-i\phi/2} - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ──X─╭Rϕ────X─╭●────╭●─╭RY───╭●─┤ - 1: ──X─╰●─────X─╰Rϕ───╰X─╰●────╰X─┤ - - """ - x = re.ResourceX.resource_rep() - ctrl_phase_shift = re.ResourceControlledPhaseShift.resource_rep() - cnot = re.ResourceCNOT.resource_rep() - cry = re.ResourceCRY.resource_rep() - - gate_types = {} - gate_types[x] = 4 - gate_types[ctrl_phase_shift] = 2 - gate_types[cnot] = 2 - gate_types[cry] = 1 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - -class ResourceSingleExcitationPlus(qml.SingleExcitationPlus, re.ResourceOperator): - r"""Resource class for the SingleExcitationPlus gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int] or int): the wires the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are obtained by decomposing the following matrix into fundamental gates. - - .. math:: U_+(\phi) = \begin{bmatrix} - e^{i\phi/2} & 0 & 0 & 0 \\ - 0 & \cos(\phi/2) & -\sin(\phi/2) & 0 \\ - 0 & \sin(\phi/2) & \cos(\phi/2) & 0 \\ - 0 & 0 & 0 & e^{i\phi/2} - \end{bmatrix}. - - The cost for implmementing this transformation is given by: - - .. code-block:: bash - - 0: ──X─╭Rϕ──X─╭●───╭●─╭RY──╭●─┤ - 1: ──X─╰●───X─╰Rϕ──╰X─╰●───╰X─┤ - - .. seealso:: :class:`~.SingleExcitationPlus` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceSingleExcitationPlus.resources() - {X: 4, ControlledPhaseShift: 2, CNOT: 2, CRY: 1} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are obtained by decomposing the following matrix into fundamental gates. - - .. math:: U_+(\phi) = \begin{bmatrix} - e^{i\phi/2} & 0 & 0 & 0 \\ - 0 & \cos(\phi/2) & -\sin(\phi/2) & 0 \\ - 0 & \sin(\phi/2) & \cos(\phi/2) & 0 \\ - 0 & 0 & 0 & e^{i\phi/2} - \end{bmatrix}. - - The cost for implmementing this transformation is given by: - - .. code-block:: bash - - 0: ──X─╭Rϕ──X─╭●───╭●─╭RY──╭●─┤ - 1: ──X─╰●───X─╰Rϕ──╰X─╰●───╰X─┤ - - """ - x = re.ResourceX.resource_rep() - ctrl_phase_shift = re.ResourceControlledPhaseShift.resource_rep() - cnot = re.ResourceCNOT.resource_rep() - cry = re.ResourceCRY.resource_rep() - - gate_types = {} - gate_types[x] = 4 - gate_types[ctrl_phase_shift] = 2 - gate_types[cnot] = 2 - gate_types[cry] = 1 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - -class ResourceDoubleExcitation(qml.DoubleExcitation, re.ResourceOperator): - r"""Resource class for the DoubleExcitation gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int]): the wires the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are obtained by decomposing the following mapping into fundamental gates. - - .. math:: - - &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle + \sin(\phi/2) |1100\rangle\\ - &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle - \sin(\phi/2) |0011\rangle, - - For the source of this decomposition, see page 17 of `"Local, Expressive, - Quantum-Number-Preserving VQE Ansatze for Fermionic Systems" `_ . - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ────╭●──H─╭●──RY───╭●──RY─────────────╭X──RY──────────╭●──RY───╭●─╭X──H──╭●────┤ - 1: ────│─────╰X──RY───│───────╭X──RY──╭X─│───RY────╭X────│───RY───╰X─│──────│─────┤ - 2: ─╭●─╰X─╭●──────────│───────│───────╰●─╰●────────│─────│───────────╰●─────╰X─╭●─┤ - 3: ─╰X──H─╰X──────────╰X──H───╰●───────────────────╰●──H─╰X──H─────────────────╰X─┤ - - .. seealso:: :class:`~.DoubleExcitation` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceDoubleExcitation.resources() - {Hadamard: 6, RY: 8, CNOT: 14} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are obtained by decomposing the following mapping into fundamental gates. - - .. math:: - - &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle + \sin(\phi/2) |1100\rangle\\ - &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle - \sin(\phi/2) |0011\rangle, - - For the source of this decomposition, see page 17 of `"Local, Expressive, - Quantum-Number-Preserving VQE Ansatze for Fermionic Systems" `_ . - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ────╭●──H─╭●──RY───╭●──RY─────────────╭X──RY──────────╭●──RY───╭●─╭X──H──╭●────┤ - 1: ────│─────╰X──RY───│───────╭X──RY──╭X─│───RY────╭X────│───RY───╰X─│──────│─────┤ - 2: ─╭●─╰X─╭●──────────│───────│───────╰●─╰●────────│─────│───────────╰●─────╰X─╭●─┤ - 3: ─╰X──H─╰X──────────╰X──H───╰●───────────────────╰●──H─╰X──H─────────────────╰X─┤ - - """ - h = re.ResourceHadamard.resource_rep() - ry = re.ResourceRY.resource_rep() - cnot = re.ResourceCNOT.resource_rep() - - gate_types = {} - gate_types[h] = 6 - gate_types[ry] = 8 - gate_types[cnot] = 14 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - -class ResourceDoubleExcitationMinus(qml.DoubleExcitationMinus, re.ResourceOperator): - r"""Resource class for the DoubleExcitationMinus gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int]): the wires the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are obtained by decomposing the following mapping into fundamental gates. - - .. math:: - - &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle - \sin(\phi/2) |1100\rangle\\ - &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle + \sin(\phi/2) |0011\rangle\\ - &|x\rangle \rightarrow e^{-i\phi/2} |x\rangle, - - Specifically, the resources are given by one :class:`~.ResourceDoubleExcitation`, one - :class:`~.ResourcePhaseShift` gate, two multi-controlled Z-gates controlled on 3 qubits, - and two multi-controlled phase shift gates controlled on 3 qubits. - - .. seealso:: :class:`~.DoubleExcitationMinus` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceDoubleExcitationMinus.resources() - {GlobalPhase: 1, DoubleExcitation: 1, C(Z,3,1,0): 2, C(PhaseShift,3,1,0): 2} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are obtained by decomposing the following mapping into fundamental gates. - - .. math:: - - &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle - \sin(\phi/2) |1100\rangle\\ - &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle + \sin(\phi/2) |0011\rangle\\ - &|x\rangle \rightarrow e^{-i\phi/2} |x\rangle, - - Specifically, the resources are given by one :class:`~.ResourceDoubleExcitation`, one - :class:`~.ResourcePhaseShift` gate, two multi-controlled Z-gates controlled on 3 qubits, - and two multi-controlled phase shift gates controlled on 3 qubits. - """ - phase = re.ResourceGlobalPhase.resource_rep() - double = re.ResourceDoubleExcitation.resource_rep() - ctrl_z = re.ResourceControlled.resource_rep(re.ResourceZ, {}, 3, 1, 0) - ctrl_phase = re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 3, 1, 0) - - gate_types = {} - gate_types[phase] = 1 - gate_types[double] = 1 - gate_types[ctrl_z] = 2 - gate_types[ctrl_phase] = 2 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - -class ResourceDoubleExcitationPlus(qml.DoubleExcitationPlus, re.ResourceOperator): - r"""Resource class for the DoubleExcitationPlus gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int]): the wires the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are obtained by decomposing the following mapping into fundamental gates. - - .. math:: - - &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle - \sin(\phi/2) |1100\rangle\\ - &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle + \sin(\phi/2) |0011\rangle\\ - &|x\rangle \rightarrow e^{-i\phi/2} |x\rangle, - - Specifically, the resources are given by one :class:`~.ResourceDoubleExcitation`, one - :class:`~.ResourcePhaseShift` gate, two multi-controlled Z-gates controlled on 3 qubits, - and two multi-controlled phase shift gates controlled on 3 qubits. - - .. seealso:: :class:`~.DoubleExcitationPlus` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceDoubleExcitationPlus.resources() - {GlobalPhase: 1, DoubleExcitation: 1, C(Z,3,1,0): 2, C(PhaseShift,3,1,0): 2} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are obtained by decomposing the following mapping into fundamental gates. - - .. math:: - - &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle - \sin(\phi/2) |1100\rangle\\ - &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle + \sin(\phi/2) |0011\rangle\\ - &|x\rangle \rightarrow e^{-i\phi/2} |x\rangle, - - Specifically, the resources are given by one :class:`~.ResourceDoubleExcitation`, one - :class:`~.ResourcePhaseShift` gate, two multi-controlled Z-gates controlled on 3 qubits, - and two multi-controlled phase shift gates controlled on 3 qubits. - """ - phase = re.ResourceGlobalPhase.resource_rep() - double = re.ResourceDoubleExcitation.resource_rep() - ctrl_z = re.ResourceControlled.resource_rep(re.ResourceZ, {}, 3, 1, 0) - ctrl_phase = re.ResourceControlled.resource_rep(re.ResourcePhaseShift, {}, 3, 1, 0) - - gate_types = {} - gate_types[phase] = 1 - gate_types[double] = 1 - gate_types[ctrl_z] = 2 - gate_types[ctrl_phase] = 2 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - -class ResourceOrbitalRotation(qml.OrbitalRotation, re.ResourceOperator): - r"""Resource class for the OrbitalRotation gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int]): the wires the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are obtained by decomposing the following mapping into fundamental gates. - - .. math:: - &|\Phi_{0}\rangle = \cos(\phi/2)|\Phi_{0}\rangle - \sin(\phi/2)|\Phi_{1}\rangle\\ - &|\Phi_{1}\rangle = \cos(\phi/2)|\Phi_{0}\rangle + \sin(\phi/2)|\Phi_{1}\rangle, - - Specifically, the resources are given by two :class:`~.ResourceSingleExcitation` gates and - two :class:`~.ResourceFermionicSWAP` gates. - - .. seealso:: :class:`~.OrbitalRotation` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceOrbitalRotation.resources() - {FermionicSWAP: 2, SingleExcitation: 2} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are obtained by decomposing the following mapping into fundamental gates. - - .. math:: - &|\Phi_{0}\rangle = \cos(\phi/2)|\Phi_{0}\rangle - \sin(\phi/2)|\Phi_{1}\rangle\\ - &|\Phi_{1}\rangle = \cos(\phi/2)|\Phi_{0}\rangle + \sin(\phi/2)|\Phi_{1}\rangle, - - Specifically, the resources are given by two :class:`~.ResourceSingleExcitation` gates and - two :class:`~.ResourceFermionicSWAP` gates. - """ - fermionic_swap = re.ResourceFermionicSWAP.resource_rep() - single_excitation = re.ResourceSingleExcitation.resource_rep() - - gate_types = {} - gate_types[fermionic_swap] = 2 - gate_types[single_excitation] = 2 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) - - -class ResourceFermionicSWAP(qml.FermionicSWAP, re.ResourceOperator): - r"""Resource class for the FermionicSWAP gate. - - Args: - phi (float): rotation angle :math:`\phi` - wires (Sequence[int]): the wires the operation acts on - id (str or None): String representing the operation (optional) - - Resources: - The resources are obtained by decomposing the following matrix into fundamental gates. - - .. math:: U(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & e^{i \phi/2} \cos(\phi/2) & -ie^{i \phi/2} \sin(\phi/2) & 0 \\ - 0 & -ie^{i \phi/2} \sin(\phi/2) & e^{i \phi/2} \cos(\phi/2) & 0 \\ - 0 & 0 & 0 & e^{i \phi} - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ──H─╭MultiRZ──H──RX─╭MultiRZ──RX──RZ─╭GlobalPhase─┤ - 1: ──H─╰MultiRZ──H──RX─╰MultiRZ──RX──RZ─╰GlobalPhase─┤ - - .. seealso:: :class:`~.FermionicSWAP` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceFermionicSWAP.resources() - {Hadamard: 4, MultiRZ: 2, RX: 4, RZ: 2, GlobalPhase: 1} - """ - - @staticmethod - def _resource_decomp(**kwargs): - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Resources: - The resources are obtained by decomposing the following matrix into fundamental gates. - - .. math:: U(\phi) = \begin{bmatrix} - 1 & 0 & 0 & 0 \\ - 0 & e^{i \phi/2} \cos(\phi/2) & -ie^{i \phi/2} \sin(\phi/2) & 0 \\ - 0 & -ie^{i \phi/2} \sin(\phi/2) & e^{i \phi/2} \cos(\phi/2) & 0 \\ - 0 & 0 & 0 & e^{i \phi} - \end{bmatrix}. - - The cost for implementing this transformation is given by: - - .. code-block:: bash - - 0: ──H─╭MultiRZ──H──RX─╭MultiRZ──RX──RZ─╭GlobalPhase─┤ - 1: ──H─╰MultiRZ──H──RX─╰MultiRZ──RX──RZ─╰GlobalPhase─┤ - - """ - h = re.ResourceHadamard.resource_rep() - multi_rz = re.ResourceMultiRZ.resource_rep(num_wires=2) - rx = re.ResourceRX.resource_rep() - rz = re.ResourceRZ.resource_rep() - phase = re.ResourceGlobalPhase.resource_rep() - - gate_types = {} - gate_types[h] = 4 - gate_types[multi_rz] = 2 - gate_types[rx] = 4 - gate_types[rz] = 2 - gate_types[phase] = 1 - - return gate_types - - @property - def resource_params(self): - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: Empty dictionary. The resources of this operation don't depend on any additional parameters. - """ - return {} - - @classmethod - def resource_rep(cls): - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - return re.CompressedResourceOp(cls, {}) diff --git a/pennylane/labs/resource_estimation/resource_container.py b/pennylane/labs/resource_estimation/resource_container.py deleted file mode 100644 index 24d2bc86cff..00000000000 --- a/pennylane/labs/resource_estimation/resource_container.py +++ /dev/null @@ -1,347 +0,0 @@ -# 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. -r"""Base classes for resource estimation.""" -import copy -from collections import defaultdict - -from pennylane.decomposition.resources import CompressedResourceOp as _CompressedResourceOp -from pennylane.labs.resource_estimation import ResourceOperator - - -class CompressedResourceOp(_CompressedResourceOp): # pylint: disable=too-few-public-methods - r"""Instantiate the light weight class corresponding to the operator type and parameters. - - Args: - op_type (Type): the class object of an operation which inherits from :class:`~.ResourceOperator` - params (dict): a dictionary containing the minimal pairs of parameter names and values - required to compute the resources for the given operator - - .. details:: - - This representation is the minimal amount of information required to estimate resources for the operator. - - **Example** - - >>> op_tp = CompressedResourceOp(ResourceHadamard, {"num_wires":1}) - >>> print(op_tp) - Hadamard(num_wires=1) - """ - - def __init__(self, op_type, params: dict, name=None) -> None: - r"""Instantiate the light weight class corresponding to the operator type and parameters. - - Args: - op_type (Type): the class object for an operation which inherits from :class:`~.ResourceOperator` - params (dict): a dictionary containing the minimal pairs of parameter names and values - required to compute the resources for the given operator - - .. details:: - - This representation is the minimal amount of information required to estimate resources for the operator. - - **Example** - - >>> op_tp = CompressedResourceOp(ResourceHadamard, {"num_wires":1}) - >>> print(op_tp) - Hadamard(num_wires=1) - """ - if not issubclass(op_type, ResourceOperator): - raise TypeError(f"op_type must be a subclass of ResourceOperator. Got {op_type}.") - - super().__init__(op_type, params) - self._name = name or op_type.tracking_name(**params) - - def __repr__(self) -> str: - return self._name - - -# @dataclass -class Resources: - r"""Contains attributes which store key resources such as number of gates, number of wires, and gate types. - - Args: - num_wires (int): number of qubits - num_gates (int): number of gates - gate_types (dict): dictionary storing operation names (str) as keys - and the number of times they are used in the circuit (int) as values - - .. details:: - - The resources being tracked can be accessed as class attributes. - Additionally, the :class:`~.Resources` instance can be nicely displayed in the console. - - **Example** - - >>> r = Resources( - ... num_wires=2, - ... num_gates=2, - ... gate_types={"Hadamard": 1, "CNOT": 1} - ... ) - >>> print(r) - wires: 2 - gates: 2 - gate_types: - {'Hadamard': 1, 'CNOT': 1} - """ - - def __init__(self, num_wires: int = 0, num_gates: int = 0, gate_types: dict = None): - gate_types = gate_types or {} - - self.num_wires = num_wires - self.num_gates = num_gates - self.gate_types = ( - gate_types - if (isinstance(gate_types, defaultdict) and isinstance(gate_types.default_factory, int)) - else defaultdict(int, gate_types) - ) - - def __add__(self, other: "Resources") -> "Resources": - """Add two resources objects in series""" - return add_in_series(self, other) - - def __eq__(self, other: "Resources") -> bool: - """Test if two resource objects are equal""" - if self.num_wires != other.num_wires: - return False - if self.num_gates != other.num_gates: - return False - - return self.gate_types == other.gate_types - - def __mul__(self, scalar: int) -> "Resources": - """Scale a resources object in series""" - return mul_in_series(self, scalar) - - __rmul__ = __mul__ # same implementation - - def __iadd__(self, other: "Resources") -> "Resources": - """Add two resources objects in series""" - return add_in_series(self, other, in_place=True) - - def __imull__(self, scalar: int) -> "Resources": - """Scale a resources object in series""" - return mul_in_series(self, scalar, in_place=True) - - def __str__(self): - """String representation of the Resources object.""" - keys = ["wires", "gates"] - vals = [self.num_wires, self.num_gates] - items = "\n".join([str(i) for i in zip(keys, vals)]) - items = items.replace("('", "") - items = items.replace("',", ":") - items = items.replace(")", "") - - gate_type_str = ", ".join( - [f"'{gate_name}': {count}" for gate_name, count in self.gate_types.items()] - ) - items += "\ngate_types:\n{" + gate_type_str + "}" - return items - - def __repr__(self): - """Compact string representation of the Resources object""" - return { - "gate_types": self.gate_types, - "num_gates": self.num_gates, - "num_wires": self.num_wires, - }.__repr__() - - def _ipython_display_(self): - """Displays __str__ in ipython instead of __repr__""" - print(str(self)) - - -def add_in_series(first: Resources, other: Resources, in_place=False) -> Resources: - r"""Add two resources assuming the circuits are executed in series. - - Args: - first (Resources): first resource object to combine - other (Resources): other resource object to combine with - in_place (bool): determines if the first Resources are modified in place (default False) - - Returns: - Resources: combined resources - """ - new_wires = max(first.num_wires, other.num_wires) - new_gates = first.num_gates + other.num_gates - new_gate_types = _combine_dict(first.gate_types, other.gate_types, in_place=in_place) - - if in_place: - first.num_wires = new_wires - first.num_gates = new_gates - return first - - return Resources(new_wires, new_gates, new_gate_types) - - -def add_in_parallel(first: Resources, other: Resources, in_place=False) -> Resources: - r"""Add two resources assuming the circuits are executed in parallel. - - Args: - first (Resources): first resource object to combine - other (Resources): other resource object to combine with - in_place (bool): determines if the first Resources are modified in place (default False) - - Returns: - Resources: combined resources - """ - new_wires = first.num_wires + other.num_wires - new_gates = first.num_gates + other.num_gates - new_gate_types = _combine_dict(first.gate_types, other.gate_types, in_place=in_place) - - if in_place: - first.num_wires = new_wires - first.num_gates = new_gates - return first - - return Resources(new_wires, new_gates, new_gate_types) - - -def mul_in_series(first: Resources, scalar: int, in_place=False) -> Resources: - r"""Multiply the resources by a scalar assuming the circuits are executed in series. - - Args: - first (Resources): first resource object to combine - scalar (int): integer value to scale the resources by - in_place (bool): determines if the first Resources are modified in place (default False) - - Returns: - Resources: combined resources - """ - new_gates = scalar * first.num_gates - new_gate_types = _scale_dict(first.gate_types, scalar, in_place=in_place) - - if in_place: - first.num_gates = new_gates - return first - - return Resources(first.num_wires, new_gates, new_gate_types) - - -def mul_in_parallel(first: Resources, scalar: int, in_place=False) -> Resources: - r"""Multiply the resources by a scalar assuming the circuits are executed in parallel. - - Args: - first (Resources): first resource object to combine - scalar (int): integer value to scale the resources by - in_place (bool): determines if the first Resources are modified in place (default False) - - Returns: - Resources: combined resources - """ - new_wires = scalar * first.num_wires - new_gates = scalar * first.num_gates - new_gate_types = _scale_dict(first.gate_types, scalar, in_place=in_place) - - if in_place: - first.num_wires = new_wires - first.num_gates = new_gates - return first - - return Resources(new_wires, new_gates, new_gate_types) - - -def substitute( - initial_resources: Resources, gate_name: str, replacement_resources: Resources, in_place=False -) -> Resources: - """Replaces a specified gate in a :class:`~.resource.Resources` object with the contents of - another :class:`~.resource.Resources` object. - - Args: - initial_resources (Resources): the resources to be modified - gate_name (str): the name of the operation to be replaced - replacement (Resources): the resources to be substituted instead of the gate - in_place (bool): determines if the initial resources are modified in place or if a new copy is - created - - Returns: - Resources: the updated :class:`~.Resources` after substitution - - .. details:: - - **Example** - - In this example we replace the resources for the :code:`RX` gate: - - .. code-block:: python3 - - from pennylane.labs.resource_estimation import Resources - - replace_gate_name = "RX" - - initial_resources = Resources( - num_wires = 2, - num_gates = 3, - gate_types = {"RX": 2, "CNOT": 1}, - ) - - replacement_rx_resources = Resources( - num_wires = 1, - num_gates = 7, - gate_types = {"Hadamard": 3, "S": 4}, - ) - - Executing the substitution produces: - - >>> from pennylane.labs.resource_estimation import substitute - >>> res = substitute( - ... initial_resources, replace_gate_name, replacement_rx_resources, - ... ) - >>> print(res) - wires: 2 - gates: 15 - gate_types: - {'CNOT': 1, 'Hadamard': 6, 'S': 8} - """ - - count = initial_resources.gate_types.get(gate_name, 0) - - if count > 0: - new_gates = initial_resources.num_gates - count + (count * replacement_resources.num_gates) - - replacement_gate_types = _scale_dict( - replacement_resources.gate_types, count, in_place=in_place - ) - new_gate_types = _combine_dict( - initial_resources.gate_types, replacement_gate_types, in_place=in_place - ) - new_gate_types.pop(gate_name) - - if in_place: - initial_resources.num_gates = new_gates - return initial_resources - - return Resources(initial_resources.num_wires, new_gates, new_gate_types) - - return initial_resources - - -def _combine_dict(dict1: defaultdict, dict2: defaultdict, in_place=False): - r"""Private function which combines two dictionaries together.""" - combined_dict = dict1 if in_place else copy.copy(dict1) - - for k, v in dict2.items(): - combined_dict[k] += v - - return combined_dict - - -def _scale_dict(dict1: defaultdict, scalar: int, in_place=False): - r"""Private function which scales the values in a dictionary.""" - - combined_dict = dict1 if in_place else copy.copy(dict1) - - for k in combined_dict: - combined_dict[k] *= scalar - - return combined_dict diff --git a/pennylane/labs/resource_estimation/resource_operator.py b/pennylane/labs/resource_estimation/resource_operator.py deleted file mode 100644 index b5205c51d66..00000000000 --- a/pennylane/labs/resource_estimation/resource_operator.py +++ /dev/null @@ -1,187 +0,0 @@ -# Copyright 2025 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. -r"""Abstract base class for resource operators.""" -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Callable, Dict - -if TYPE_CHECKING: - from pennylane.labs.resource_estimation import CompressedResourceOp - -# pylint: disable=unused-argument - - -class ResourceOperator(ABC): - r"""Abstract class that defines the methods a PennyLane Operator - must implement in order to be used for resource estimation. - - .. details:: - - **Example** - - A PennyLane Operator can be extended for resource estimation by creating a new class that - inherits from both the ``Operator`` and ``ResourceOperator``. Here is an example showing how to - extend ``qml.QFT`` for resource estimation. - - .. code-block:: python - - import pennylane as qml - from pennylane.labs.resource_estimation import CompressedResourceOp, ResourceOperator - - class ResourceQFT(qml.QFT, ResourceOperator): - - @staticmethod - def _resource_decomp(num_wires) -> Dict[CompressedResourceOp, int]: - gate_types = {} - - hadamard = ResourceHadamard.resource_rep() - swap = ResourceSWAP.resource_rep() - ctrl_phase_shift = ResourceControlledPhaseShift.resource_rep() - - gate_types[hadamard] = num_wires - gate_types[swap] = num_wires // 2 - gate_types[ctrl_phase_shift] = num_wires*(num_wires - 1) // 2 - - return gate_types - - @property - def resource_params(self, num_wires) -> dict: - return {"num_wires": num_wires} - - @classmethod - def resource_rep(cls, num_wires) -> CompressedResourceOp: - params = {"num_wires": num_wires} - return CompressedResourceOp(cls, params) - - Which can be instantiated as a normal operation, but now contains the resources: - - .. code-block:: bash - - >>> op = ResourceQFT(range(3)) - >>> op.resources(**op.resource_params) - {Hadamard: 3, SWAP: 1, ControlledPhaseShift: 3} - - """ - - @staticmethod - @abstractmethod - def _resource_decomp(*args, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts.""" - - @classmethod - def resources(cls, *args, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts.""" - return cls._resource_decomp(*args, **kwargs) - - @classmethod - def set_resources(cls, new_func: Callable) -> None: - """Set a custom resource method.""" - cls.resources = new_func - - @property - @abstractmethod - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to - compute a compressed representation""" - - @classmethod - @abstractmethod - def resource_rep(cls, *args, **kwargs) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation.""" - - def resource_rep_from_op(self) -> CompressedResourceOp: - r"""Returns a compressed representation directly from the operator""" - return self.__class__.resource_rep(**self.resource_params) - - @classmethod - def adjoint_resource_decomp(cls, *args, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the adjoint of the operator. - - Raises: - ResourcesNotDefined: no resources implemented by default - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated values are the counts. - """ - raise ResourcesNotDefined - - @classmethod - def controlled_resource_decomp( - cls, num_ctrl_wires: int, num_ctrl_values: int, num_work_wires: int, *args, **kwargs - ) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for a controlled version of the operator. - - Args: - num_ctrl_wires (int): the number of qubits the operation is controlled on - num_ctrl_values (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state - num_work_wires (int): the number of additional qubits that can be used for decomposition - - Raises: - ResourcesNotDefined: no resources implemented by default - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated values are the counts. - """ - - raise ResourcesNotDefined - - @classmethod - def pow_resource_decomp(cls, z: int, *args, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - - Raises: - ResourcesNotDefined: no resources implemented by default - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated values are the counts. - """ - raise ResourcesNotDefined - - @classmethod - def exp_resource_decomp( - cls, scalar: complex, num_steps: int, *args, **kwargs - ) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for the exponentiated operator. - - Args: - scalar (complex): complex coefficient of the operator in the exponent - num_steps (int): number of trotter steps to use when decomposing the expoentiated operator - - Raises: - ResourcesNotDefined: no resources implemented by default - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated values are the counts. - """ - raise ResourcesNotDefined - - @classmethod - def tracking_name(cls, *args, **kwargs) -> str: - r"""Returns a name used to track the operator during resource estimation.""" - return cls.__name__.replace("Resource", "") - - def tracking_name_from_op(self) -> str: - r"""Returns the tracking name built with the operator's parameters.""" - return self.__class__.tracking_name(**self.resource_params) - - -class ResourcesNotDefined(Exception): - r"""Exception to be raised when a ``ResourceOperator`` does not implement _resource_decomp""" diff --git a/pennylane/labs/resource_estimation/resource_tracking.py b/pennylane/labs/resource_estimation/resource_tracking.py deleted file mode 100644 index a2b04921a0a..00000000000 --- a/pennylane/labs/resource_estimation/resource_tracking.py +++ /dev/null @@ -1,319 +0,0 @@ -# Copyright 2025 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. -r"""Core resource tracking logic.""" -from collections import defaultdict -from collections.abc import Callable -from functools import singledispatch, wraps -from typing import Dict, Iterable, List, Set, Union - -import pennylane as qml -from pennylane.operation import Operation -from pennylane.queuing import AnnotatedQueue -from pennylane.tape import QuantumScript -from pennylane.wires import Wires - -from .resource_container import CompressedResourceOp, Resources -from .resource_operator import ResourceOperator - -# pylint: disable=dangerous-default-value,protected-access - -# user-friendly gateset for visual checks and initial compilation -_StandardGateSet = { - "X", - "Y", - "Z", - "Hadamard", - "SWAP", - "CNOT", - "S", - "T", - "Toffoli", - "RX", - "RY", - "RZ", - "PhaseShift", -} - -# practical/realistic gateset for useful compilation of circuits -DefaultGateSet = { - "Hadamard", - "CNOT", - "S", - "T", - "Toffoli", -} - -# parameters for further configuration of the decompositions -resource_config = { - "error_rx": 10e-3, - "error_ry": 10e-3, - "error_rz": 10e-3, -} - - -@singledispatch -def get_resources( - obj, gate_set: Set = DefaultGateSet, config: Dict = resource_config -) -> Union[Resources, Callable]: - r"""Obtain the resources from a quantum circuit or operation in terms of the gates provided - in the gate_set. - - Args: - obj (Union[Operation, Callable, QuantumScript]): the quantum circuit or operation to obtain resources from - gate_set (Set, optional): python set of strings specifying the names of operations to track - config (Dict, optional): dictionary of additiona; configurations that specify how resources are computed - - Returns: - Resources: the total resources of the quantum circuit - - Raises: - TypeError: could not obtain resources for obj of type `type(obj)` - - **Example** - - We can track the resources of a quantum workflow by passing the quantum function defining our workflow directly - into this function. - - .. code-block:: python - - import copy - import pennylane.labs.resource_estimation as re - - def my_circuit(): - for w in range(2): - re.ResourceHadamard(w) - - re.ResourceCNOT([0, 1]) - re.ResourceRX(1.23, 0) - re.ResourceRY(-4.56, 1) - - re.ResourceQFT(wires=[0, 1, 2]) - return qml.expval(re.ResourceHadamard(2)) - - Note that we are passing a python function NOT a :class:`~.QNode`. The resources for this workflow are then obtained by: - - >>> res = re.get_resources(my_circuit)() - >>> print(res) - wires: 3 - gates: 202 - gate_types: - {'Hadamard': 5, 'CNOT': 10, 'T': 187} - - .. details:: - :title: Usage Details - - Users can provide custom gatesets to track resources with. Consider :code:`my_circuit()` from above: - - >>> my_gateset = {"Hadamard", "RX", "RY", "QFT(3)", "CNOT"} - >>> print(re.get_resources(my_circuit, gate_set = my_gateset)()) - wires: 3 - gates: 6 - gate_types: - {'Hadamard': 2, 'CNOT': 1, 'RX': 1, 'RY': 1, 'QFT(3)': 1} - - We can also obtain resources for individual operations and quantum tapes in a similar manner: - - >>> op = re.ResourceRX(1.23, 0) - >>> print(re.get_resources(op)) - wires: 1 - gates: 17 - gate_types: - {'T': 17} - - Finally, we can modify the config values listed in the global :code:`labs.resource_estimation.resource_config` - dictionary to have finegrain control of how the resources are computed. - - >>> re.resource_config - {'error_rx': 0.01, 'error_ry': 0.01, 'error_rz': 0.01} - >>> - >>> my_config = copy.copy(re.resource_config) - >>> my_config["error_rx"] = 0.001 - >>> - >>> print(re.get_resources(op, config=my_config)) - wires: 1 - gates: 21 - gate_types: - {'T': 21} - - """ - - raise TypeError( - f"Could not obtain resources for obj of type {type(obj)}. obj must be one of Operation, Callable or QuantumScript" - ) - - -@get_resources.register -def resources_from_operation( - obj: Operation, gate_set: Set = DefaultGateSet, config: Dict = resource_config -) -> Resources: - """Get resources from an operation""" - - if isinstance(obj, ResourceOperator): - cp_rep = obj.resource_rep_from_op() - - gate_counts_dict = defaultdict(int) - _counts_from_compressed_res_op(cp_rep, gate_counts_dict, gate_set=gate_set, config=config) - gate_types = _clean_gate_counts(gate_counts_dict) - - n_gates = sum(gate_types.values()) - return Resources(num_wires=len(obj.wires), num_gates=n_gates, gate_types=gate_types) - - res = Resources() # TODO: Add implementation here! - return res - - -@get_resources.register -def resources_from_qfunc( - obj: Callable, gate_set: Set = DefaultGateSet, config: Dict = resource_config -) -> Callable: - """Get resources from a quantum function which queues operations""" - - @wraps(obj) - def wrapper(*args, **kwargs): - with AnnotatedQueue() as q: - obj(*args, **kwargs) - - operations = tuple(op for op in q.queue if isinstance(op, Operation)) - compressed_res_ops_lst = _operations_to_compressed_reps(operations) - - initial_gate_set = set.union(gate_set, _StandardGateSet) - - gate_counts_dict = defaultdict(int) - for cmp_rep_op in compressed_res_ops_lst: - _counts_from_compressed_res_op( - cmp_rep_op, gate_counts_dict, gate_set=initial_gate_set, config=config - ) - - # Validation: - condensed_gate_counts = defaultdict(int) - for sub_cmp_rep, counts in gate_counts_dict.items(): - _counts_from_compressed_res_op( - sub_cmp_rep, condensed_gate_counts, scalar=counts, gate_set=gate_set, config=config - ) - - clean_gate_counts = _clean_gate_counts(condensed_gate_counts) - num_gates = sum(clean_gate_counts.values()) - num_wires = len(Wires.all_wires(tuple(op.wires for op in operations))) - return Resources(num_wires=num_wires, num_gates=num_gates, gate_types=clean_gate_counts) - - return wrapper - - -@get_resources.register -def resources_from_tape( - obj: QuantumScript, gate_set: Set = DefaultGateSet, config: Dict = resource_config -) -> Resources: - """Get resources from a quantum tape""" - num_wires = obj.num_wires - operations = obj.operations - compressed_res_ops_lst = _operations_to_compressed_reps(operations) - - gate_counts_dict = defaultdict(int) - for cmp_rep_op in compressed_res_ops_lst: - _counts_from_compressed_res_op( - cmp_rep_op, gate_counts_dict, gate_set=_StandardGateSet, config=config - ) - - # Validation: - condensed_gate_counts = defaultdict(int) - for sub_cmp_rep, counts in gate_counts_dict.items(): - _counts_from_compressed_res_op( - sub_cmp_rep, condensed_gate_counts, scalar=counts, gate_set=gate_set, config=config - ) - - clean_gate_counts = _clean_gate_counts(condensed_gate_counts) - num_gates = sum(clean_gate_counts.values()) - - return Resources(num_wires=num_wires, num_gates=num_gates, gate_types=clean_gate_counts) - - -def _counts_from_compressed_res_op( - cp_rep: CompressedResourceOp, - gate_counts_dict, - gate_set: Set, - scalar: int = 1, - config: Dict = resource_config, -) -> None: - """Modifies the `gate_counts_dict` argument by adding the (scaled) resources of the operation provided. - - Args: - cp_rep (CompressedResourceOp): operation in compressed representation to extract resources from - gate_counts_dict (Dict): base dictionary to modify with the resource counts - gate_set (Set): the set of operations to track resources with respect to - scalar (int, optional): optional scalar to multiply the counts. Defaults to 1. - config (Dict, optional): additional parameters to specify the resources from an operator. Defaults to resource_config. - """ - ## If op in gate_set add to resources - if cp_rep._name in gate_set: - gate_counts_dict[cp_rep] += scalar - return - - ## Else decompose cp_rep using its resource decomp [cp_rep --> dict[cp_rep: counts]] and extract resources - resource_decomp = cp_rep.op_type.resources(config=config, **cp_rep.params) - - for sub_cp_rep, counts in resource_decomp.items(): - _counts_from_compressed_res_op( - sub_cp_rep, gate_counts_dict, scalar=scalar * counts, gate_set=gate_set, config=config - ) - - return - - -def _temp_map_func(op: Operation) -> ResourceOperator: - """Temp map function""" - raise NotImplementedError - - -def _clean_gate_counts(gate_counts: Dict[CompressedResourceOp, int]) -> Dict[str, int]: - """Map resources with gate_types made from CompressedResourceOps - into one which tracks just strings of operations. - - Args: - gate_counts (Dict[CompressedResourceOp, int]): gate counts in terms of compressed resource ops - - Returns: - Dict[str, int]: gate counts in terms of names of operations - """ - clean_gate_counts = defaultdict(int) - - for cmp_res_op, counts in gate_counts.items(): - clean_gate_counts[cmp_res_op._name] += counts - - return clean_gate_counts - - -@qml.QueuingManager.stop_recording() -def _operations_to_compressed_reps(ops: Iterable[Operation]) -> List[CompressedResourceOp]: - """Convert the sequence of operations to a list of compressed resource ops. - - Args: - ops (Iterable[Operation]): set of operations to convert - - Returns: - List[CompressedResourceOp]: set of converted compressed resource ops - """ - cmp_rep_ops = [] - for op in ops: - if isinstance(op, ResourceOperator): - cmp_rep_ops.append(op.resource_rep_from_op()) - - else: - try: - cmp_rep_ops.append(_temp_map_func(op).resource_rep_from_op()) - - except NotImplementedError: - decomp = op.decomposition() - cmp_rep_ops.extend(_operations_to_compressed_reps(decomp)) - - return cmp_rep_ops diff --git a/pennylane/labs/resource_estimation/templates/__init__.py b/pennylane/labs/resource_estimation/templates/__init__.py deleted file mode 100644 index 58bbf37c40b..00000000000 --- a/pennylane/labs/resource_estimation/templates/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2025 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. -r"""This module contains resource operators for PennyLane templates.""" - -from .subroutines import ( - ResourceQFT, - ResourceQuantumPhaseEstimation, - ResourceQPE, - ResourceBasisRotation, - ResourcePrepSelPrep, - ResourceQubitization, - ResourceQROM, - ResourceReflection, - ResourceSelect, - ResourceControlledSequence, - ResourceModExp, - ResourceMultiplier, - ResourcePhaseAdder, - ResourceAmplitudeAmplification, -) - -from .trotter import ResourceTrotterProduct, ResourceTrotterizedQfunc, resource_trotterize - -from .stateprep import ( - ResourceSuperposition, - ResourceStatePrep, - ResourceBasisState, - ResourceMottonenStatePreparation, -) diff --git a/pennylane/labs/resource_estimation/templates/stateprep.py b/pennylane/labs/resource_estimation/templates/stateprep.py deleted file mode 100644 index 291ac605189..00000000000 --- a/pennylane/labs/resource_estimation/templates/stateprep.py +++ /dev/null @@ -1,393 +0,0 @@ -# Copyright 2025 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. -r"""Resource operators for PennyLane state preparation templates.""" -import math -from typing import Dict - -import pennylane as qml -from pennylane.labs import resource_estimation as re -from pennylane.labs.resource_estimation import CompressedResourceOp, ResourceOperator - -# pylint: disable=arguments-differ, protected-access - - -class ResourceStatePrep(qml.StatePrep, ResourceOperator): - """Resource class for StatePrep. - - Args: - state (array[complex] or csr_matrix): the state vector to prepare - wires (Sequence[int] or int): the wire(s) the operation acts on - pad_with (float or complex): if not ``None``, ``state`` is padded with this constant to be of size :math:`2^n`, where - :math:`n` is the number of wires. - normalize (bool): whether to normalize the state vector. To represent a valid quantum state vector, the L2-norm - of ``state`` must be one. The argument ``normalize`` can be set to ``True`` to normalize the state automatically. - id (str): custom label given to an operator instance, - can be useful for some applications where the instance has to be identified - validate_norm (bool): whether to validate the norm of the input state - - Resource Parameters: - * num_wires (int): the number of wires that the operation acts on - - Resources: - Uses the resources as defined in the :class:`~.ResourceMottonenStatePreperation` template. - - .. seealso:: :class:`~.StatePrep` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceStatePrep.resources(num_wires=3) - {MottonenStatePrep(3): 1} - """ - - @staticmethod - def _resource_decomp(num_wires, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - num_wires (int): the number of wires that the operation acts on - - Resources: - Uses the resources as defined in the :class:`~.ResourceMottonenStatePreperation` template. - """ - return {re.ResourceMottonenStatePreparation.resource_rep(num_wires): 1} - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * num_wires (int): the number of wires that the operation acts on - """ - return {"num_wires": len(self.wires)} - - @classmethod - def resource_rep(cls, num_wires) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - num_wires (int): the number of wires that the operation acts on - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = {"num_wires": num_wires} - return CompressedResourceOp(cls, params) - - @classmethod - def tracking_name(cls, num_wires) -> str: - return f"StatePrep({num_wires})" - - -class ResourceMottonenStatePreparation(qml.MottonenStatePreparation, ResourceOperator): - """Resource class for the MottonenStatePreparation template. - - Args: - state_vector (tensor_like): Input array of shape ``(2^n,)``, where ``n`` is the number of wires - the state preparation acts on. The input array must be normalized. - wires (Iterable): wires that the template acts on - - Resource Parameters: - * num_wires(int): the number of wires that the operation acts on - - Resources: - Using the resources as described in `Mottonen et al. (2008) `_. - The resources are defined as :math:`2^{N+2} - 5` :class:`~.ResourceRZ` gates and - :math:`2^{N+2} - 4N - 4` :class:`~.ResourceCNOT` gates. - - .. seealso:: :class:`~.MottonenStatePreperation` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceMottonenStatePreparation.resources(num_wires=3) - {RZ: 27, CNOT: 16} - """ - - @staticmethod - def _resource_decomp(num_wires, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - num_wires(int): the number of wires that the operation acts on - - Resources: - Using the resources as described in `Mottonen et al. (2008) `_. - The resources are defined as :math:`2^{N+2} - 5` :class:`~.ResourceRZ` gates and - :math:`2^{N+2} - 4N - 4` :class:`~.ResourceCNOT` gates. - """ - gate_types = {} - rz = re.ResourceRZ.resource_rep() - cnot = re.ResourceCNOT.resource_rep() - - r_count = 2 ** (num_wires + 2) - 5 - cnot_count = 2 ** (num_wires + 2) - 4 * num_wires - 4 - - if r_count: - gate_types[rz] = r_count - - if cnot_count: - gate_types[cnot] = cnot_count - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * num_wires(int): the number of wires that the operation acts on - """ - return {"num_wires": len(self.wires)} - - @classmethod - def resource_rep(cls, num_wires) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - num_wires(int): the number of wires that the operation acts on - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = {"num_wires": num_wires} - return CompressedResourceOp(cls, params) - - @classmethod - def tracking_name(cls, num_wires) -> str: - return f"MottonenStatePrep({num_wires})" - - -class ResourceSuperposition(qml.Superposition, ResourceOperator): - """Resource class for the Superposition template. - - Args: - coeffs (tensor-like[float]): normalized coefficients of the superposition - bases (tensor-like[int]): basis states of the superposition - wires (Sequence[int]): wires that the operator acts on - work_wire (Union[Wires, int, str]): the auxiliary wire used for the permutation - - Resource Parameters: - * num_stateprep_wires (int): the number of wires used for the operation - * num_basis_states (int): the number of basis states of the superposition - * size_basis_state (int): the size of each basis state - - Resources: - The resources are computed following the PennyLane decomposition of - the class :class:`~.Superposition`. - - We use the following (somewhat naive) assumptions to approximate the - resources: - - - The MottonenStatePreparation routine is assumed for the state prep - component. - - The permutation block requires 2 multi-controlled X gates and a - series of CNOT gates. On average we will be controlling on and flipping - half the number of bits in :code:`size_basis`. (i.e for any given basis - state, half will be ones and half will be zeros). - - If the number of basis states provided spans the set of all basis states, - then we don't need to permute. In general, there is a probability associated - with not needing to permute wires if the basis states happen to match, we - estimate this quantity aswell. - - .. seealso:: :class:`~.Superposition` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceSuperposition.resources(num_stateprep_wires=3, num_basis_states=3, size_basis_state=3) - {MottonenStatePrep(3): 1, CNOT: 2, MultiControlledX: 4} - """ - - @staticmethod - def _resource_decomp( - num_stateprep_wires, num_basis_states, size_basis_state, **kwargs - ) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - num_stateprep_wires (int): the number of wires used for the operation - num_basis_states (int): the number of basis states of the superposition - size_basis_state (int): the size of each basis state - - Resources: - The resources are computed following the PennyLane decomposition of - the class :class:`~.Superposition`. - - We use the following (somewhat naive) assumptions to approximate the - resources: - - - The MottonenStatePreparation routine is assumed for the state prep - component. - - The permutation block requires 2 multi-controlled X gates and a - series of CNOT gates. On average we will be controlling on and flipping - half the number of bits in :code:`size_basis`. (i.e for any given basis - state, half will be ones and half will be zeros). - - If the number of basis states provided spans the set of all basis states, - then we don't need to permute. In general, there is a probability associated - with not needing to permute wires if the basis states happen to match, we - estimate this quantity aswell. - - """ - gate_types = {} - msp = re.ResourceMottonenStatePreparation.resource_rep(num_stateprep_wires) - gate_types[msp] = 1 - - cnot = re.ResourceCNOT.resource_rep() - num_zero_ctrls = size_basis_state // 2 - multi_x = re.ResourceMultiControlledX.resource_rep( - num_ctrl_wires=size_basis_state, - num_ctrl_values=num_zero_ctrls, - num_work_wires=0, - ) - - basis_size = 2**size_basis_state - prob_matching_basis_states = num_basis_states / basis_size - num_permutes = round(num_basis_states * (1 - prob_matching_basis_states)) - - if num_permutes: - gate_types[cnot] = num_permutes * ( - size_basis_state // 2 - ) # average number of bits to flip - gate_types[multi_x] = 2 * num_permutes # for compute and uncompute - - return gate_types - - @property - def resource_params(self) -> Dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * num_stateprep_wires (int): the number of wires used for the operation - * num_basis_states (int): the number of basis states of the superposition - * size_basis_state (int): the size of each basis state - """ - bases = self.hyperparameters["bases"] - num_basis_states = len(bases) - size_basis_state = len(bases[0]) # assuming they are all the same size - num_stateprep_wires = math.ceil(math.log2(len(self.coeffs))) - - return { - "num_stateprep_wires": num_stateprep_wires, - "num_basis_states": num_basis_states, - "size_basis_state": size_basis_state, - } - - @classmethod - def resource_rep( - cls, num_stateprep_wires, num_basis_states, size_basis_state - ) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - num_stateprep_wires (int): the number of wires used for the operation - num_basis_states (int): the number of basis states of the superposition - size_basis_state (int): the size of each basis state - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = { - "num_stateprep_wires": num_stateprep_wires, - "num_basis_states": num_basis_states, - "size_basis_state": size_basis_state, - } - return CompressedResourceOp(cls, params) - - -class ResourceBasisState(qml.BasisState, ResourceOperator): - r"""Resource class for the BasisState template. - - Args: - state (tensor_like): Binary input of shape ``(len(wires), )``. For example, if ``state=np.array([0, 1, 0])`` or ``state=2`` (equivalent to 010 in binary), the quantum system will be prepared in the state :math:`|010 \rangle`. - - wires (Sequence[int] or int): the wire(s) the operation acts on - id (str): Custom label given to an operator instance. Can be useful for some applications where the instance has to be identified. - - Resource Parameters: - * num_bit_flips (int): number of qubits in the :math:`|1\rangle` state - - Resources: - The resources for BasisState are according to the decomposition found in qml.BasisState. - - .. seealso:: :class:`~.BasisState` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceBasisState.resources(num_bit_flips = 6) - {X: 6} - """ - - @staticmethod - def _resource_decomp( - num_bit_flips, - **kwargs, - ) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - num_bit_flips (int): number of qubits in the :math:`|1\rangle` state - - Resources: - The resources for BasisState are according to the decomposition found in qml.BasisState. - """ - gate_types = {} - x = re.ResourceX.resource_rep() - gate_types[x] = num_bit_flips - - return gate_types - - @property - def resource_params(self) -> Dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * num_bit_flips (int): number of qubits in the :math:`|1\rangle` state - """ - num_bit_flips = sum(self.parameters[0]) - return {"num_bit_flips": num_bit_flips} - - @classmethod - def resource_rep(cls, num_bit_flips) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - num_bit_flips (int): number of qubits in the :math:`|1\rangle` state - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - - params = {"num_bit_flips": num_bit_flips} - return CompressedResourceOp(cls, params) - - @classmethod - def tracking_name(cls, num_bit_flips) -> str: - return f"BasisState({num_bit_flips})" diff --git a/pennylane/labs/resource_estimation/templates/subroutines.py b/pennylane/labs/resource_estimation/templates/subroutines.py deleted file mode 100644 index d2f7e8578aa..00000000000 --- a/pennylane/labs/resource_estimation/templates/subroutines.py +++ /dev/null @@ -1,1635 +0,0 @@ -# Copyright 2025 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. -r"""Resource operators for PennyLane subroutine templates.""" -import math -from collections import defaultdict -from typing import Dict - -import pennylane as qml -from pennylane import numpy as qnp -from pennylane.labs import resource_estimation as re -from pennylane.labs.resource_estimation import CompressedResourceOp, ResourceOperator - -# pylint: disable=arguments-differ, protected-access - - -class ResourceQFT(qml.QFT, ResourceOperator): - r"""Resource class for QFT. - - Args: - wires (int or Iterable[Number, str]]): the wire(s) the operation acts on - - Resource Parameters: - * num_wires (int): the number of qubits the operation acts upon - - Resources: - The resources are obtained from the standard decomposition of QFT as presented - in (chapter 5) `Nielsen, M.A. and Chuang, I.L. (2011) Quantum Computation and Quantum Information - `_. - - .. seealso:: :class:`~.QFT` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceQFT.resources(num_wires=3) - {Hadamard: 3, SWAP: 1, ControlledPhaseShift: 3} - """ - - @staticmethod - def _resource_decomp(num_wires, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - num_wires (int): the number of qubits the operation acts upon - - Resources: - The resources are obtained from the standard decomposition of QFT as presented - in (Chapter 5) `Nielsen, M.A. and Chuang, I.L. (2011) Quantum Computation and Quantum Information - `_. - - """ - gate_types = {} - - hadamard = re.ResourceHadamard.resource_rep() - swap = re.ResourceSWAP.resource_rep() - ctrl_phase_shift = re.ResourceControlledPhaseShift.resource_rep() - - gate_types[hadamard] = num_wires - gate_types[swap] = num_wires // 2 - gate_types[ctrl_phase_shift] = num_wires * (num_wires - 1) // 2 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * num_wires (int): the number of qubits the operation acts upon - """ - return {"num_wires": len(self.wires)} - - @classmethod - def resource_rep(cls, num_wires) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - num_wires (int): the number of qubits the operation acts upon - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = {"num_wires": num_wires} - return CompressedResourceOp(cls, params) - - @staticmethod - def tracking_name(num_wires) -> str: - r"""Returns the tracking name built with the operator's parameters.""" - return f"QFT({num_wires})" - - -class ResourceControlledSequence(qml.ControlledSequence, re.ResourceOperator): - """Resource class for the ControlledSequence template. - - Args: - base (Operator): the phase estimation unitary, specified as an :class:`~.Operator` - control (Union[Wires, Sequence[int], or int]): the wires to be used for control - - Resource Parameters: - * base_class (ResourceOperator): The type of the operation corresponding to the operator. - * base_params (dict): A dictionary of parameters required to obtain the resources for the operator. - * num_ctrl_wires (int): the number of control wires - - Resources: - The resources are obtained from the standard decomposition of :class:`~.ControlledSequence`. - - .. seealso:: :class:`~.ControlledSequence` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceControlledSequence.resources( - ... base_class=re.ResourceHadamard, - ... base_params={}, - ... num_ctrl_wires=2 - ... ) - {C(Hadamard,1,0,0): 3} - """ - - @staticmethod - def _resource_decomp( - base_class, base_params, num_ctrl_wires, **kwargs - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - base_class (ResourceOperator): The type of the operation corresponding to the - operator. - base_params (dict): A dictionary of parameters required to obtain the resources for - the operator. - num_ctrl_wires (int): the number of control wires - - Resources: - The resources are obtained from the standard decomposition of :class:`~.ControlledSequence`. - - """ - return { - re.ResourceControlled.resource_rep(base_class, base_params, 1, 0, 0): 2**num_ctrl_wires - - 1 - } - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * base_class (ResourceOperator): The type of the operation corresponding to the operator. - * base_params (dict): A dictionary of parameters required to obtain the resources for the operator. - * num_ctrl_wires (int): the number of control wires - """ - return { - "base_class": type(self.base), - "base_params": self.base.resource_params, - "num_ctrl_wires": len(self.control_wires), - } - - @classmethod - def resource_rep(cls, base_class, base_params, num_ctrl_wires) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - base_class (ResourceOperator): The type of the operation corresponding to the - operator. - base_params (dict): A dictionary of parameters required to obtain the resources for - the operator. - num_ctrl_wires (int): the number of control wires - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - return re.CompressedResourceOp( - cls, - { - "base_class": base_class, - "base_params": base_params, - "num_ctrl_wires": num_ctrl_wires, - }, - ) - - @staticmethod - def tracking_name(base_class, base_params, num_ctrl_wires) -> str: - base_name = base_class.tracking_name(**base_params) - return f"ControlledSequence({base_name}, {num_ctrl_wires})" - - -class ResourcePhaseAdder(qml.PhaseAdder, re.ResourceOperator): - r"""Resource class for the PhaseAdder template. - - Args: - k (int): the number that needs to be added - x_wires (Sequence[int]): the wires the operation acts on. The number of wires must be enough - for a binary representation of the value being targeted, :math:`x`. In some cases an additional - wire is needed, see usage details below. The number of wires also limits the maximum - value for `mod`. - mod (int): the modulo for performing the addition. If not provided, it will be set to its maximum value, :math:`2^{\text{len(x_wires)}}`. - work_wire (Sequence[int] or int): the auxiliary wire to use for the addition. Optional - when `mod` is :math:`2^{len(x\_wires)}`. Defaults to empty tuple. - - Resource Parameters: - * mod (int): the module for performing the addition - * num_x_wires (int): the number of wires the operation acts on - - Resources: - The resources are obtained from the standard decomposition of :class:`~.PhaseAdder`. - - .. seealso:: :class:`~.PhaseAdder` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourcePhaseAdder.resources( - ... mod=3, - ... num_x_wires=5 - ... ) - {QFT(5): 2, - Adjoint(QFT(5)): 2, - PhaseShift: 10, - Adjoint(PhaseShift): 10, - C(PhaseShift,1,0,0): 5, - CNOT: 1, - MultiControlledX: 1} - """ - - @staticmethod - def _resource_decomp(mod, num_x_wires, **kwargs) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - mod (int): the module for performing the addition - num_x_wires (int): the number of wires the operation acts on - - Resources: - The resources are obtained from the standard decomposition of :class:`~.PhaseAdder`. - - """ - if mod == 2**num_x_wires: - return {re.ResourcePhaseShift.resource_rep(): num_x_wires} - - qft = ResourceQFT.resource_rep(num_x_wires) - qft_dag = re.ResourceAdjoint.resource_rep( - ResourceQFT, - {"num_wires": num_x_wires}, - ) - - phase_shift = re.ResourcePhaseShift.resource_rep() - phase_shift_dag = re.ResourceAdjoint.resource_rep( - re.ResourcePhaseShift, - {}, - ) - ctrl_phase_shift = re.ResourceControlledPhaseShift.resource_rep() - - cnot = re.ResourceCNOT.resource_rep() - multix = re.ResourceMultiControlledX.resource_rep(1, 0, 1) - - gate_types = {} - gate_types[qft] = 2 - gate_types[qft_dag] = 2 - gate_types[phase_shift] = 2 * num_x_wires - gate_types[phase_shift_dag] = 2 * num_x_wires - gate_types[ctrl_phase_shift] = num_x_wires - gate_types[cnot] = 1 - gate_types[multix] = 1 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Resource Parameters: - mod (int): the module for performing the addition - num_x_wires (int): the number of wires the operation acts on - - Returns: - dict: A dictionary containing the resource parameters: - * mod (int): the module for performing the addition - * num_x_wires (int): the number of wires the operation acts on - """ - return { - "mod": self.hyperparameters["mod"], - "num_x_wires": len(self.hyperparameters["x_wires"]), - } - - @classmethod - def resource_rep(cls, mod, num_x_wires) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - mod (int): the module for performing the addition - num_x_wires (int): the number of wires the operation acts on - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - - return re.CompressedResourceOp(cls, {"mod": mod, "num_x_wires": num_x_wires}) - - -class ResourceMultiplier(qml.Multiplier, re.ResourceOperator): - """Resource class for the Multiplier template. - - Args: - k (int): the number that needs to be multiplied - x_wires (Sequence[int]): the wires the operation acts on. The number of wires must be enough for encoding `x` in the computational basis. The number of wires also limits the maximum value for `mod`. - mod (int): the modulo for performing the multiplication. If not provided, it will be set to its maximum value, :math:`2^{\text{len(x_wires)}}`. - work_wires (Sequence[int]): the auxiliary wires to use for the multiplication. If :math:`mod=2^{\text{len(x_wires)}}`, the number of auxiliary wires must be ``len(x_wires)``. Otherwise ``len(x_wires) + 2`` auxiliary wires are needed. - - Resource Parameters: - * mod (int): the module for performing the multiplication - * num_work_wires (int): the number of work wires used for the multiplication. - * num_x_wires (int): the number of wires the operation acts on. - - Resources: - The resources are obtained from the standard decomposition of :class:`~.Multiplier`. - - .. seealso:: :class:`~.Multiplier` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceMultiplier.resources( - ... mod=3, - ... num_work_wires=5, - ... num_x_wires=5 - ... ) - {QFT(4): 2, - Adjoint(QFT(4)): 2, - ControlledSequence(PhaseAdder, 5): 1, - Adjoint(ControlledSequence(PhaseAdder, 5)): 1, - CNOT: 3} - """ - - @staticmethod - def _resource_decomp( - mod, num_work_wires, num_x_wires, **kwargs - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - mod (int): the module for performing the multiplication - num_work_wires (int): the number of work wires used for the multiplication. - num_x_wires (int): the number of wires the operation acts on. - - Resources: - The resources are obtained from the standard decomposition of :class:`~.Multiplier`. - - """ - if mod == 2**num_x_wires: - num_aux_wires = num_x_wires - num_aux_swap = num_x_wires - else: - num_aux_wires = num_work_wires - 1 - num_aux_swap = num_aux_wires - 1 - - qft = ResourceQFT.resource_rep(num_aux_wires) - qft_dag = re.ResourceAdjoint.resource_rep( - ResourceQFT, - {"num_wires": num_aux_wires}, - ) - - sequence = ResourceControlledSequence.resource_rep( - ResourcePhaseAdder, - {}, - num_x_wires, - ) - - sequence_dag = re.ResourceAdjoint.resource_rep( - ResourceControlledSequence, - { - "base_class": ResourcePhaseAdder, - "base_params": {}, - "num_ctrl_wires": num_x_wires, - }, - ) - - cnot = re.ResourceCNOT.resource_rep() - - gate_types = {} - gate_types[qft] = 2 - gate_types[qft_dag] = 2 - gate_types[sequence] = 1 - gate_types[sequence_dag] = 1 - gate_types[cnot] = min(num_x_wires, num_aux_swap) - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * mod (int): The modulus for performing the multiplication. - * num_work_wires (int): The number of work wires used. - * num_x_wires (int): The number of wires the operation acts on. - """ - return { - "mod": self.hyperparameters["mod"], - "num_work_wires": len(self.hyperparameters["work_wires"]), - "num_x_wires": len(self.hyperparameters["x_wires"]), - } - - @classmethod - def resource_rep(cls, mod, num_work_wires, num_x_wires) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - mod (int): the module for performing the multiplication - num_work_wires (int): the number of work wires used for the multiplication. - num_x_wires (int): the number of wires the operation acts on. - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - return re.CompressedResourceOp( - cls, {"mod": mod, "num_work_wires": num_work_wires, "num_x_wires": num_x_wires} - ) - - -class ResourceModExp(qml.ModExp, re.ResourceOperator): - r"""Resource class for the :class:`~.ModExp` template. - - Args: - x_wires (Sequence[int]): the wires that store the integer :math:`x` - output_wires (Sequence[int]): the wires that store the operator result. These wires also encode :math:`b`. - base (int): integer that needs to be exponentiated - mod (int): the modulo for performing the exponentiation. If not provided, it will be set to its maximum value, :math:`2^{\text{len(output_wires)}}` - work_wires (Sequence[int]): the auxiliary wires to use for the exponentiation. If - :math:`mod=2^{\text{len(output_wires)}}`, the number of auxiliary wires must be ``len(output_wires)``. Otherwise - ``len(output_wires) + 2`` auxiliary wires are needed. Defaults to empty tuple. - - Resource Parameters: - * mod (int): the modulo for performing the modular exponentiation - * num_output_wires (int): the number of output wires used to encode the integer :math:`b \cdot base^x \; \text{mod} \; mod` in the computational basis - * num_work_wires (int): the number of work wires used to perform the modular exponentiation operation - * num_x_wires (int): the number of wires used to encode the integer :math:`x < mod` in the computational basis - - Resources: - The resources are obtained from the standard decomposition of :class:`~.ModExp`. - - .. seealso:: :class:`~.ModExp` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceModExp.resources( - ... mod=3, - ... num_output_wires=5, - ... num_work_wires=5, - ... num_x_wires=5 - ... ) - {C(QFT(4),1,0,0): 62, - C(Adjoint(QFT(4)),1,0,0): 62, - C(ControlledSequence(PhaseAdder, 5),1,0,0): 31, - C(Adjoint(ControlledSequence(PhaseAdder, 5)),1,0,0): 31, - C(CNOT,1,0,0): 93} - """ - - @staticmethod - def _resource_decomp( - mod, num_output_wires, num_work_wires, num_x_wires, **kwargs - ) -> Dict[re.CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - mod (int): the module for performing the exponentiation - num_output_wires (int): the number of output wires used to encode the integer :math:`b \cdot base^x \; \text{mod} \; mod` in the computational basis - num_work_wires (int): the number of work wires used to perform the modular exponentiation operation - num_x_wires (int): the number of wires used to encode the integer :math:`x < mod` in the computational basis - - Resources: - The resources are obtained from the standard decomposition of :class:`~.ModExp`. - - """ - mult_resources = ResourceMultiplier._resource_decomp(mod, num_work_wires, num_output_wires) - gate_types = {} - - for comp_rep, _ in mult_resources.items(): - new_rep = re.ResourceControlled.resource_rep(comp_rep.op_type, comp_rep.params, 1, 0, 0) - - # cancel out QFTs from consecutive Multipliers - if comp_rep._name in ("QFT", "Adjoint(QFT)"): - gate_types[new_rep] = 1 - else: - gate_types[new_rep] = mult_resources[comp_rep] * ((2**num_x_wires) - 1) - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * mod (int): the module for performing the exponentiation - * num_output_wires (int): the number of output wires used to encode the integer :math:`b \cdot base^x \; \text{mod} \; mod` in the computational basis - * num_work_wires (int): the number of work wires used to perform the modular exponentiation operation - * num_x_wires (int): the number of wires used to encode the integer :math:`x < mod` in the computational basis - """ - return { - "mod": self.hyperparameters["mod"], - "num_output_wires": len(self.hyperparameters["output_wires"]), - "num_work_wires": len(self.hyperparameters["work_wires"]), - "num_x_wires": len(self.hyperparameters["x_wires"]), - } - - @classmethod - def resource_rep( - cls, mod, num_output_wires, num_work_wires, num_x_wires - ) -> re.CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - mod (int): the module for performing the exponentiation - num_output_wires (int): the number of output wires used to encode the integer :math:`b \cdot base^x \; \text{mod} \; mod` - in the computational basis - num_work_wires (int): the number of work wires used to perform the modular exponentiation - operation - num_x_wires (int): the number of wires used to encode the integer :math:`x < mod` in the - computational basis - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - return re.CompressedResourceOp( - cls, - { - "mod": mod, - "num_output_wires": num_output_wires, - "num_work_wires": num_work_wires, - "num_x_wires": num_x_wires, - }, - ) - - -class ResourceQuantumPhaseEstimation(qml.QuantumPhaseEstimation, ResourceOperator): - r"""Resource class for QuantumPhaseEstimation (QPE). - - Args: - unitary (array or Operator): the phase estimation unitary, specified as a matrix or an - :class:`~.Operator` - target_wires (Union[Wires, Sequence[int], or int]): the target wires to apply the unitary. - If the unitary is specified as an operator, the target wires should already have been - defined as part of the operator. In this case, target_wires should not be specified. - estimation_wires (Union[Wires, Sequence[int], or int]): the wires to be used for phase - estimation - - Resource Parameters: - * base_class (ResourceOperator): The type of the operation corresponding to the phase estimation unitary. - * base_params (dict): A dictionary of parameters required to obtain the resources for the phase estimation unitary. - * num_estimation_wires (int): the number of wires used for measuring out the phase - - Resources: - The resources are obtained from the standard decomposition of QPE as presented - in (Section 5.2) `Nielsen, M.A. and Chuang, I.L. (2011) Quantum Computation and Quantum - Information `_. - - .. seealso:: :class:`~.QuantumPhaseEstimation` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceQuantumPhaseEstimation.resources( - ... base_class=re.ResourceQFT, - ... base_params={"num_wires": 3}, - ... num_estimation_wires=3, - ... ) - {Hadamard: 3, Adjoint(QFT(3)): 1, C(QFT(3),1,0,0): 7} - """ - - @staticmethod - def _resource_decomp( - base_class, base_params, num_estimation_wires, **kwargs - ) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - base_class (Type(ResourceOperator)): The type of the operation corresponding to the - phase estimation unitary. - base_params (dict): A dictionary of parameters required to obtain the resources for - the phase estimation unitary. - num_estimation_wires (int): the number of wires used for measuring out the phase - - Resources: - The resources are obtained from the standard decomposition of QPE as presented - in (section 5.2) `Nielsen, M.A. and Chuang, I.L. (2011) Quantum Computation and Quantum - Information `_. - """ - gate_types = {} - - hadamard = re.ResourceHadamard.resource_rep() - adj_qft = re.ResourceAdjoint.resource_rep(ResourceQFT, {"num_wires": num_estimation_wires}) - ctrl_op = re.ResourceControlled.resource_rep(base_class, base_params, 1, 0, 0) - - gate_types[hadamard] = num_estimation_wires - gate_types[adj_qft] = 1 - gate_types[ctrl_op] = (2**num_estimation_wires) - 1 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * base_class (Type(ResourceOperator)): The type of the operation corresponding to the phase estimation unitary. - * base_params (dict): A dictionary of parameters required to obtain the resources for the phase estimation unitary. - * num_estimation_wires (int): the number of wires used for measuring out the phase - """ - op = self.hyperparameters["unitary"] - num_estimation_wires = len(self.hyperparameters["estimation_wires"]) - - if not isinstance(op, re.ResourceOperator): - raise TypeError( - f"Can't obtain QPE resources when the base unitary {op} isn't an instance" - " of ResourceOperator" - ) - - return { - "base_class": type(op), - "base_params": op.resource_params, - "num_estimation_wires": num_estimation_wires, - } - - @classmethod - def resource_rep( - cls, - base_class, - base_params, - num_estimation_wires, - ) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - base_class (Type(ResourceOperator)): The type of the operation corresponding to the - phase estimation unitary. - base_params (dict): A dictionary of parameters required to obtain the resources for - the phase estimation unitary. - num_estimation_wires (int): the number of wires used for measuring out the phase - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = { - "base_class": base_class, - "base_params": base_params, - "num_estimation_wires": num_estimation_wires, - } - return CompressedResourceOp(cls, params) - - @staticmethod - def tracking_name(base_class, base_params, num_estimation_wires) -> str: - r"""Returns the tracking name built with the operator's parameters.""" - base_name = base_class.tracking_name(**base_params) - return f"QPE({base_name}, {num_estimation_wires})" - - -ResourceQPE = ResourceQuantumPhaseEstimation # Alias for ease of typing -r"""Resource class for QuantumPhaseEstimation (QPE). - -Args: - unitary (array or Operator): the phase estimation unitary, specified as a matrix or an - :class:`~.Operator` - target_wires (Union[Wires, Sequence[int], or int]): the target wires to apply the unitary. - If the unitary is specified as an operator, the target wires should already have been - defined as part of the operator. In this case, target_wires should not be specified. - estimation_wires (Union[Wires, Sequence[int], or int]): the wires to be used for phase - estimation - -Resource Parameters: - * base_class (ResourceOperator): The type of the operation corresponding to the phase estimation unitary. - * base_params (dict): A dictionary of parameters required to obtain the resources for the phase estimation unitary. - * num_estimation_wires (int): the number of wires used for measuring out the phase - -Resources: - The resources are obtained from the standard decomposition of QPE as presented - in (Section 5.2) `Nielsen, M.A. and Chuang, I.L. (2011) Quantum Computation and Quantum - Information `_. - -.. seealso:: :class:`~.QuantumPhaseEstimation` - -**Example** - -The resources for this operation are computed using: - ->>> re.ResourceQuantumPhaseEstimation.resources( -... base_class=re.ResourceQFT, -... base_params={"num_wires": 3}, -... num_estimation_wires=3, -... ) -{Hadamard: 3, Adjoint(QFT(3)): 1, C(QFT(3),1,0,0): 7} -""" - - -class ResourceBasisRotation(qml.BasisRotation, ResourceOperator): - r"""Resource class for the BasisRotation gate. - - Args: - wires (Iterable[Any]): wires that the operator acts on - unitary_matrix (array): matrix specifying the basis transformation - check (bool): test unitarity of the provided `unitary_matrix` - - Resource Parameters: - * dim_N (int): The dimensions of the input :code:`unitary_matrix`. This is computed as the number of columns of the matrix. - - Resources: - The resources are obtained from the construction scheme given in `Optica, 3, 1460 (2016) - `_. Specifically, - the resources are given as :math:`dim_N * (dim_N - 1) / 2` instances of the - :class:`~.ResourceSingleExcitation` gate, and :math:`dim_N * (1 + (dim_N - 1) / 2)` instances - of the :class:`~.ResourcePhaseShift` gate. - - .. seealso:: :class:`~.BasisRotation` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceBasisRotation.resources(dim_N=3) - {PhaseShift: 6.0, SingleExcitation: 3.0} - """ - - @staticmethod - def _resource_decomp(dim_N, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - dim_N (int): The dimensions of the input :code:`unitary_matrix`. This is computed - as the number of columns of the matrix. - - Resources: - The resources are obtained from the construction scheme given in `Optica, 3, 1460 (2016) - `_. Specifically, - the resources are given as :math:`dim_N * (dim_N - 1) / 2` instances of the - :class:`~.ResourceSingleExcitation` gate, and :math:`dim_N * (1 + (dim_N - 1) / 2)` instances - of the :class:`~.ResourcePhaseShift` gate. - """ - gate_types = {} - phase_shift = re.ResourcePhaseShift.resource_rep() - single_excitation = re.ResourceSingleExcitation.resource_rep() - - se_count = dim_N * (dim_N - 1) / 2 - ps_count = dim_N + se_count - - gate_types[phase_shift] = ps_count - gate_types[single_excitation] = se_count - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * dim_N (int): The dimensions of the input :code:`unitary_matrix`. This is computed as the number of columns of the matrix. - """ - unitary_matrix = self.parameters[0] - return {"dim_N": qml.math.shape(unitary_matrix)[0]} - - @classmethod - def resource_rep(cls, dim_N) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - dim_N (int): The dimensions of the input :code:`unitary_matrix`. This is computed - as the number of columns of the matrix. - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = {"dim_N": dim_N} - return CompressedResourceOp(cls, params) - - @classmethod - def tracking_name(cls, dim_N) -> str: - r"""Returns the tracking name built with the operator's parameters.""" - return f"BasisRotation({dim_N})" - - -class ResourceSelect(qml.Select, ResourceOperator): - r"""Resource class for the Select gate. - - Args: - ops (list[Operator]): operations to apply - control (Sequence[int]): the wires controlling which operation is applied - id (str or None): String representing the operation (optional) - - Resource Parameters: - * cmpr_ops (list[CompressedResourceOp]): The list of operators, in the compressed representation, to be applied according to the selected qubits. - - Resources: - The resources correspond directly to the definition of the operation. Specifically, - for each operator in :code:`cmpr_ops`, the cost is given as a controlled version of the operator - controlled on the associated bitstring. - - .. seealso:: :class:`~.Select` - - **Example** - - The resources for this operation are computed using: - - >>> ops_lst = [re.ResourceX.resource_rep(), re.ResourceQFT.resource_rep(num_wires=3)] - >>> re.ResourceSelect.resources(cmpr_ops=ops_lst) - defaultdict(, {X: 2, C(X,1,0,0): 1, C(QFT(3),1,0,0): 1}) - """ - - @staticmethod - def _resource_decomp(cmpr_ops, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - cmpr_ops (list[CompressedResourceOp]): The list of operators, in the compressed - representation, to be applied according to the selected qubits. - - Resources: - The resources correspond directly to the definition of the operation. Specifically, - for each operator in :code:`cmpr_ops`, the cost is given as a controlled version of the operator - controlled on the associated bitstring. - """ - gate_types = defaultdict(int) - x = re.ResourceX.resource_rep() - - num_ops = len(cmpr_ops) - num_ctrl_wires = int(qnp.ceil(qnp.log2(num_ops))) - num_total_ctrl_possibilities = 2**num_ctrl_wires # 2^n - - num_zero_controls = num_total_ctrl_possibilities // 2 - gate_types[x] = num_zero_controls * 2 # conjugate 0 controls - - for cmp_rep in cmpr_ops: - ctrl_op = re.ResourceControlled.resource_rep( - cmp_rep.op_type, cmp_rep.params, num_ctrl_wires, 0, 0 - ) - gate_types[ctrl_op] += 1 - - return gate_types - - @staticmethod - def resources_for_ui(cmpr_ops, **kwargs): # pylint: disable=unused-argument - r"""The resources for a select implementation taking advantage of the unary iterator trick. - - The resources are based on the analysis in `Babbush et al. (2018) `_ section III.A, - 'Unary Iteration and Indexed Operations'. See Figures 4, 6, and 7. - - Note: This implementation assumes we have access to :math:`S + 1` additional work qubits, - where :math:`S = \ceil{log_{2}(N)}` and :math:`N` is the number of batches of unitaries - to select. - """ - gate_types = defaultdict(int) - x = re.ResourceX.resource_rep() - cnot = re.ResourceCNOT.resource_rep() - toffoli = re.ResourceToffoli.resource_rep() - - num_ops = len(cmpr_ops) - - for cmp_rep in cmpr_ops: - ctrl_op = re.ResourceControlled.resource_rep(cmp_rep.op_type, cmp_rep.params, 1, 0, 0) - gate_types[ctrl_op] += 1 - - gate_types[x] = 2 * (num_ops - 1) # conjugate 0 controlled toffolis - gate_types[cnot] = num_ops - 1 - gate_types[toffoli] = 2 * (num_ops - 1) - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * cmpr_ops (list[CompressedResourceOp]): The list of operators, in the compressed representation, to be applied according to the selected qubits. - """ - ops = self.hyperparameters["ops"] - cmpr_ops = tuple(op.resource_rep_from_op() for op in ops) - return {"cmpr_ops": cmpr_ops} - - @classmethod - def resource_rep(cls, cmpr_ops) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - cmpr_ops (list[CompressedResourceOp]): The list of operators, in the compressed - representation, to be applied according to the selected qubits. - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = {"cmpr_ops": cmpr_ops} - return CompressedResourceOp(cls, params) - - -class ResourcePrepSelPrep(qml.PrepSelPrep, ResourceOperator): - r"""Resource class for PrepSelPrep gate. - - Args: - lcu (Union[.Hamiltonian, .Sum, .Prod, .SProd, .LinearCombination]): The operator - written as a linear combination of unitaries. - control (Iterable[Any], Wires): The control qubits for the PrepSelPrep operator. - - Resource Parameters: - * cmpr_ops (tuple[CompressedResourceOp]): The list of operators, in the compressed representation, which correspond to the unitaries in the LCU to be blockencoded. - - Resources: - The resources correspond directly to the definition of the operation. Specifically, - the resources are given as one instance of :class:`~.ResourceSelect`, which is conjugated by - a pair of :class:`~.ResourceStatePrep` operations. - - .. seealso:: :class:`~.PrepSelPrep` - - **Example** - - The resources for this operation are computed using: - - >>> ops_tup = (re.ResourceX.resource_rep(), re.ResourceQFT.resource_rep(num_wires=3)) - >>> re.ResourcePrepSelPrep.resources(cmpr_ops=ops_tup) - {StatePrep(1): 1, Select: 1, Adjoint(StatePrep(1)): 1} - - """ - - @staticmethod - def _resource_decomp(cmpr_ops, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - cmpr_ops (tuple[CompressedResourceOp]): The list of operators, in the compressed - representation, which correspond to the unitaries in the LCU to be blockencoded. - - Resources: - The resources correspond directly to the definition of the operation. Specifically, - the resources are given as one instance of :class:`~.ResourceSelect`, which is conjugated by - a pair of :class:`~.ResourceStatePrep` operations. - """ - gate_types = {} - - num_ops = len(cmpr_ops) - num_wires = int(math.ceil(math.log2(num_ops))) - - prep = re.ResourceStatePrep.resource_rep(num_wires) - sel = ResourceSelect.resource_rep(cmpr_ops) - prep_dag = re.ResourceAdjoint.resource_rep(re.ResourceStatePrep, {"num_wires": num_wires}) - - gate_types[prep] = 1 - gate_types[sel] = 1 - gate_types[prep_dag] = 1 - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * cmpr_ops (tuple[CompressedResourceOp]): The list of operators, in the compressed representation, which correspond to the unitaries in the LCU to be blockencoded. - """ - ops = self.hyperparameters["ops"] - cmpr_ops = tuple(op.resource_rep_from_op() for op in ops) - return {"cmpr_ops": cmpr_ops} - - @classmethod - def resource_rep(cls, cmpr_ops) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - cmpr_ops (tuple[CompressedResourceOp]): The list of operators, in the compressed - representation, to be applied according to the selected qubits. - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = {"cmpr_ops": cmpr_ops} - return CompressedResourceOp(cls, params) - - @classmethod - def pow_resource_decomp(cls, z, cmpr_ops) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources for an operator raised to a power. - - Args: - z (int): the power that the operator is being raised to - cmpr_ops (tuple[CompressedResourceOp]): The list of operators, in the compressed - representation, to be applied according to the selected qubits. - - Resources: - The resources are derived from the following identity. If an operation :math:`\hat{A}` - can be expressed as :math:`\hat{A} \ = \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger}` - then the operation squared can be expressed as: - - .. math:: - - \begin{align} - \hat{A}^{2} \ &= \ \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger} \cdot \hat{U} \cdot \hat{B} \cdot \hat{U}^{\dagger} \\ - \hat{A}^{2} \ &= \ \hat{U} \cdot \hat{B} \cdot \hat{B} \cdot \hat{U}^{\dagger} \\ - \hat{A}^{2} \ &= \ \hat{U} \cdot \hat{B}^{2} \cdot \hat{U}^{\dagger}, - \end{align} - - this holds for any integer power :math:`z`. In general, the resources are given by :math:`z` - instances of :class:`~.ResourceSelect` conjugated by a pair of :class:`~.ResourceStatePrep` - operations. - - Returns: - Dict[CompressedResourceOp, int]: The keys are the operators and the associated - values are the counts. - """ - gate_types = {} - - num_ops = len(cmpr_ops) - num_wires = int(math.ceil(math.log2(num_ops))) - - prep = re.ResourceStatePrep.resource_rep(num_wires) - pow_sel = re.ResourcePow.resource_rep(ResourceSelect, {"cmpr_ops": cmpr_ops}, z) - prep_dag = re.ResourceAdjoint.resource_rep(re.ResourceStatePrep, {"num_wires": num_wires}) - - gate_types[prep] = 1 - gate_types[pow_sel] = 1 - gate_types[prep_dag] = 1 - return gate_types - - -class ResourceReflection(qml.Reflection, ResourceOperator): - r"""Resource class for the Reflection gate. - - Args: - U (Operator): the operator that prepares the state :math:`|\Psi\rangle` - alpha (float): the angle of the operator, default is :math:`\pi` - reflection_wires (Any or Iterable[Any]): subsystem of wires on which to reflect, the - default is ``None`` and the reflection will be applied on the ``U`` wires. - - Resource Parameters: - * base_class (Type(ResourceOperator)): The type of the operation used to prepare the state we will be reflecting over. - * base_params (dict): A dictionary of parameters required to obtain the resources for the state preparation operator. - * num_ref_wires (int): The number of qubits for the subsystem on which the reflection is applied. - - Resources: - The resources correspond directly to the definition of the operation. The operator is - built as follows: - - .. math:: - - \text{R}(U, \alpha) = -I + (1 - e^{i\alpha}) |\Psi\rangle \langle \Psi| = U(-I + (1 - e^{i\alpha}) |0\rangle \langle 0|)U^{\dagger}. - - The central block is obtained through a controlled :class:`~.ResourcePhaseShift` operator and - a :class:`~.ResourceGlobalPhase` which are conjugated with a pair of :class:`~.ResourceX` gates. - Finally, the block is conjugated with the state preparation unitary :math:`U`. - - .. seealso:: :class:`~.Reflection` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceReflection.resources( - ... base_class=re.ResourceQFT, - ... base_params={"num_wires": 3}, - ... num_ref_wires=3, - ... ) - {X: 2, GlobalPhase: 1, QFT(3): 1, Adjoint(QFT(3)): 1, C(PhaseShift,2,2,0): 1} - """ - - @staticmethod - def _resource_decomp( - base_class, base_params, num_ref_wires, **kwargs - ) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - base_class (Type(ResourceOperator)): The type of the operation used to prepare the - state we will be reflecting over. - base_params (dict): A dictionary of parameters required to obtain the resources for - the state preparation operator. - num_ref_wires (int): The number of qubits for the subsystem on which the reflection is - applied. - - Resources: - The resources correspond directly to the definition of the operation. The operator is - built as follows: - - .. math:: - - \text{R}(U, \alpha) = -I + (1 - e^{i\alpha}) |\Psi\rangle \langle \Psi| = U(-I + (1 - e^{i\alpha}) |0\rangle \langle 0|)U^{\dagger}. - - The central block is obtained through a controlled :class:`~.ResourcePhaseShift` operator and - a :class:`~.ResourceGlobalPhase` which are conjugated with a pair of :class:`~.ResourceX` gates. - Finally, the block is conjugated with the state preparation unitary :math:`U`. - """ - gate_types = {} - base = base_class.resource_rep(**base_params) - - x = re.ResourceX.resource_rep() - gp = re.ResourceGlobalPhase.resource_rep() - adj_base = re.ResourceAdjoint.resource_rep(base_class, base_params) - ps = ( - re.ResourceControlled.resource_rep( - re.ResourcePhaseShift, {}, num_ref_wires - 1, num_ref_wires - 1, 0 - ) - if num_ref_wires > 1 - else re.ResourcePhaseShift.resource_rep() - ) - - gate_types[x] = 2 - gate_types[gp] = 1 - gate_types[base] = 1 - gate_types[adj_base] = 1 - gate_types[ps] = 1 - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * base_class (Type(ResourceOperator)): The type of the operation used to prepare the state we will be reflecting over. - * base_params (dict): A dictionary of parameters required to obtain the resources for the state preparation operator. - * num_ref_wires (int): The number of qubits for the subsystem on which the reflection is applied. - """ - base_cmpr_rep = self.hyperparameters["base"].resource_rep_from_op() - num_ref_wires = len(self.hyperparameters["reflection_wires"]) - - return { - "base_class": base_cmpr_rep.op_type, - "base_params": base_cmpr_rep.params, - "num_ref_wires": num_ref_wires, - } - - @classmethod - def resource_rep(cls, base_class, base_params, num_ref_wires) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - base_class (Type(ResourceOperator)): The type of the operation used to prepare the - state we will be reflecting over. - base_params (dict): A dictionary of parameters required to obtain the resources for - the state preparation operator. - num_ref_wires (int): The number of qubits for the subsystem on which the reflection is - applied. - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = { - "base_class": base_class, - "base_params": base_params, - "num_ref_wires": num_ref_wires, - } - return CompressedResourceOp(cls, params) - - -class ResourceQubitization(qml.Qubitization, ResourceOperator): - r"""Resource class for the Qubitization gate. - - Args: - hamiltonian (Union[.Hamiltonian, .Sum, .Prod, .SProd, .LinearCombination]): The Hamiltonian written as a linear combination of unitaries. - control (Iterable[Any], Wires): The control qubits for the Qubitization operator. - - Resource Parameters: - * cmpr_ops (list[CompressedResourceOp]): The list of operators, in the compressed representation, corresponding to the unitaries of the LCU representation of the hamiltonian being qubitized. - * num_ctrl_wires (int): The number of qubits used to prepare the coefficients vector of the LCU. - - Resources: - The resources are obtained from the definition of the operation as described in (section III. C) - `Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer - `_: - - .. math:: - - Q = \text{Prep}_{\mathcal{H}}^{\dagger} \text{Sel}_{\mathcal{H}} \text{Prep}_{\mathcal{H}}(2|0\rangle\langle 0| - I). - - Specifically, the resources are given by one :class:`~.ResourcePrepSelPrep` gate and one - :class:`~.ResourceReflection` gate. - - .. seealso:: :class:`~.Qubitization` - - **Example** - - The resources for this operation are computed using: - - >>> ops_tup = (re.ResourceX.resource_rep(), re.ResourceQFT.resource_rep(num_wires=3)) - >>> re.ResourceQubitization.resources( - ... cmpr_ops=ops_tup, - ... num_ctrl_wires=2, - ... ) - {Reflection: 1, PrepSelPrep: 1} - """ - - @staticmethod - def _resource_decomp(cmpr_ops, num_ctrl_wires, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - cmpr_ops (list[CompressedResourceOp]): The list of operators, in the compressed representation, - corresponding to the unitaries of the LCU representation of the hamiltonian being qubitized. - num_ctrl_wires (int): The number of qubits used to prepare the coefficients vector of the LCU. - - Resources: - The resources are obtained from the definition of the operation as described in (section III. C) - `Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer - `_: - - .. math:: - - Q = \text{Prep}_{\mathcal{H}}^{\dagger} \text{Sel}_{\mathcal{H}} \text{Prep}_{\mathcal{H}}(2|0\rangle\langle 0| - I). - - Specifically, the resources are given by one :class:`~.ResourcePrepSelPrep` gate and one - :class:`~.ResourceReflection` gate. - """ - gate_types = {} - ref = ResourceReflection.resource_rep(re.ResourceIdentity, {}, num_ctrl_wires) - psp = ResourcePrepSelPrep.resource_rep(cmpr_ops) - - gate_types[ref] = 1 - gate_types[psp] = 1 - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * cmpr_ops (list[CompressedResourceOp]): The list of operators, in the compressed representation, corresponding to the unitaries of the LCU representation of the hamiltonian being qubitized. - * num_ctrl_wires (int): The number of qubits used to prepare the coefficients vector of the LCU. - """ - lcu = self.hyperparameters["hamiltonian"] - _, ops = lcu.terms() - - cmpr_ops = tuple(op.resource_rep_from_op() for op in ops) - num_ctrl_wires = len(self.hyperparameters["control"]) - return {"cmpr_ops": cmpr_ops, "num_ctrl_wires": num_ctrl_wires} - - @classmethod - def resource_rep(cls, cmpr_ops, num_ctrl_wires) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - cmpr_ops (list[CompressedResourceOp]): The list of operators, in the compressed representation, corresponding to the unitaries of the LCU representation of the hamiltonian being qubitized. - num_ctrl_wires (int): The number of qubits used to prepare the coefficients vector of the LCU. - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = {"cmpr_ops": cmpr_ops, "num_ctrl_wires": num_ctrl_wires} - return CompressedResourceOp(cls, params) - - -class ResourceQROM(qml.QROM, ResourceOperator): - """Resource class for the QROM template. - - Args: - bitstrings (list[str]): the bitstrings to be encoded - control_wires (Sequence[int]): the wires where the indexes are specified - target_wires (Sequence[int]): the wires where the bitstring is loaded - work_wires (Sequence[int]): the auxiliary wires used for the computation - clean (bool): if True, the work wires are not altered by operator, default is ``True`` - - Resource Parameters: - * num_bitstrings (int): the number of bitstrings that are to be encoded - * num_bit_flips (int): the number of bit flips needed for the list of bitstrings - * num_control_wires (int): the number of control wires where in the indexes are specified - * num_work_wires (int): the number of auxiliary wires used for QROM computation - * size_bitstring (int): the length of each bitstring - * clean (bool): if True, the work wires are not altered by the QROM operator - - Resources: - The resources for QROM are taken from the following two papers: - `Low et al. (2024) `_ (Figure 1.C) and - `Berry et al. (2019) `_ (Figure 4) - - We use the one-auxillary qubit version of select, instead of the built-in select - resources. - - .. seealso:: :class:`~.QROM` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceQROM.resources( - ... num_bitstrings=3, - ... num_bit_flips=7, - ... num_control_wires=5, - ... num_work_wires=5, - ... size_bitstring=3, - ... clean=True - ... ) - {Hadamard: 6, CNOT: 7, MultiControlledX: 8, X: 8, CSWAP: 12} - """ - - # pylint: disable=too-many-arguments - @staticmethod - def _resource_decomp( - num_bitstrings, - num_bit_flips, - num_control_wires, - num_work_wires, - size_bitstring, - clean, - **kwargs, - ) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - num_bitstrings (int): the number of bitstrings that are to be encoded - num_bit_flips (int): the number of bit flips needed for the list of bitstrings - num_control_wires (int): the number of control wires where in the indexes are specified - num_work_wires (int): the number of auxiliary wires used for QROM computation - size_bitstring (int): the length of each bitstring - clean (bool): if True, the work wires are not altered by the QROM operator - - Resources: - The resources for QROM are taken from the following two papers: - `Low et al. (2024) `_ (Figure 1.C) and - `Berry et al. (2019) `_ (Figure 4) - - We use the one-auxillary qubit version of select, instead of the built-in select - resources. - """ - gate_types = {} - x = re.ResourceX.resource_rep() - - if num_control_wires == 0: - gate_types[x] = num_bit_flips - return gate_types - - cnot = re.ResourceCNOT.resource_rep() - hadamard = re.ResourceHadamard.resource_rep() - - num_parallel_computations = (num_work_wires + size_bitstring) // size_bitstring - num_parallel_computations = min(num_parallel_computations, num_bitstrings) - - num_swap_wires = math.floor(math.log2(num_parallel_computations)) - num_select_wires = math.ceil(math.log2(math.ceil(num_bitstrings / (2**num_swap_wires)))) - - swap_clean_prefactor = 1 - select_clean_prefactor = 1 - - if clean: - gate_types[hadamard] = 2 * size_bitstring - swap_clean_prefactor = 4 - select_clean_prefactor = 2 - - # SELECT cost: - gate_types[cnot] = num_bit_flips # each unitary in the select is just a CNOT - - multi_x = re.ResourceMultiControlledX.resource_rep(num_select_wires, 0, 0) - num_total_ctrl_possibilities = 2**num_select_wires - gate_types[multi_x] = select_clean_prefactor * ( - 2 * num_total_ctrl_possibilities # two applications targetting the aux qubit - ) - num_zero_controls = (2 * num_total_ctrl_possibilities * num_select_wires) // 2 - gate_types[x] = select_clean_prefactor * ( - num_zero_controls * 2 # conjugate 0 controls on the multi-qubit x gates from above - ) - # SWAP cost: - ctrl_swap = re.ResourceCSWAP.resource_rep() - gate_types[ctrl_swap] = swap_clean_prefactor * ((2**num_swap_wires) - 1) * size_bitstring - - return gate_types - - @property - def resource_params(self) -> Dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * num_bitstrings (int): the number of bitstrings that are to be encoded - * num_bit_flips (int): the number of bit flips needed for the list of bitstrings - * num_control_wires (int): the number of control wires where in the indexes are specified - * num_work_wires (int): the number of auxiliary wires used for QROM computation - * size_bitstring (int): the length of each bitstring - * clean (bool): if True, the work wires are not altered by the QROM operator - """ - bitstrings = self.hyperparameters["bitstrings"] - num_bitstrings = len(bitstrings) - - num_bit_flips = 0 - for bit_string in bitstrings: - num_bit_flips += bit_string.count("1") - - num_work_wires = len(self.hyperparameters["work_wires"]) - size_bitstring = len(self.hyperparameters["target_wires"]) - num_control_wires = len(self.hyperparameters["control_wires"]) - clean = self.hyperparameters["clean"] - - return { - "num_bitstrings": num_bitstrings, - "num_bit_flips": num_bit_flips, - "num_control_wires": num_control_wires, - "num_work_wires": num_work_wires, - "size_bitstring": size_bitstring, - "clean": clean, - } - - @classmethod - def resource_rep( - cls, num_bitstrings, num_bit_flips, num_control_wires, num_work_wires, size_bitstring, clean - ) -> CompressedResourceOp: # pylint: disable=too-many-arguments - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - num_bitstrings (int): the number of bitstrings that are to be encoded - num_bit_flips (int): the number of bit flips needed for the list of bitstrings - num_control_wires (int): the number of control wires where in the indexes are specified - num_work_wires (int): the number of auxiliary wires used for QROM computation - size_bitstring (int): the length of each bitstring - clean (bool): if True, the work wires are not altered by the QROM operator - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = { - "num_bitstrings": num_bitstrings, - "num_bit_flips": num_bit_flips, - "num_control_wires": num_control_wires, - "num_work_wires": num_work_wires, - "size_bitstring": size_bitstring, - "clean": clean, - } - return CompressedResourceOp(cls, params) - - -class ResourceAmplitudeAmplification(qml.AmplitudeAmplification, ResourceOperator): - r"""Resource class for the AmplitudeAmplification template. - - Args: - U (Operator): the operator that prepares the state :math:`|\Psi\rangle` - O (Operator): the oracle that flips the sign of the state :math:`|\phi\rangle` and does nothing to the state :math:`|\phi^{\perp}\rangle` - iters (int): the number of iterations of the amplitude amplification subroutine, default is ``1`` - fixed_point (bool): whether to use the fixed-point amplitude amplification algorithm, default is ``False`` - work_wire (int): the auxiliary wire to use for the fixed-point amplitude amplification algorithm, default is ``None`` - reflection_wires (Wires): the wires to reflect on, default is the wires of ``U`` - p_min (int): the lower bound for the probability of success in fixed-point amplitude amplification, default is ``0.9`` - - Resource Parameters: - * U_op (Type[~.ResourceOperator]): the class of the operator that prepares the state :math:`|\Psi\rangle` - * U_params (dict): the parameters for the U operator - * O_op (Type[~.ResourceOperator]): the class of the oracle that flips the sign of the state :math:`|\phi\rangle` and does nothing to the state :math:`|\phi^{\perp}\rangle` - * O_params (dict): the parameters for the O operator - * iters (int): the number of iterations of the amplitude amplification subroutine - * num_ref_wires (int): the number of wires used for the reflection - * fixed_point (bool): whether to use the fixed-point amplitude amplification algorithm - - Resources: - The resources are taken from the decomposition of ``qml.AmplitudeAmplification`` class. - - .. seealso:: :class:`~.AmplitudeAmplification` - - **Example** - - The resources for this operation are computed using: - - >>> re.ResourceAmplitudeAmplification.resources( - ... U_op=re.ResourceHadamard, - ... U_params={}, - ... O_op=re.ResourceX, - ... O_params={}, - ... iters=5, - ... num_ref_wires=10, - ... fixed_point=True - ... ) - {C(X,1,0,0): 4, PhaseShift: 2, Hadamard: 8, Reflection: 2} - """ - - # pylint: disable=too-many-arguments - @staticmethod - def _resource_decomp( - U_op, - U_params, - O_op, - O_params, - iters, - num_ref_wires, - fixed_point, - **kwargs, - ) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - Args: - U_op (Operator): the operator that prepares the state :math:`|\Psi\rangle` - U_params (dict): the parameters for the U operator - O_op (Operator): the oracle that flips the sign of the state :math:`|\phi\rangle` and does nothing to the state :math:`|\phi^{\perp}\rangle` - O_params (dict): the parameters for the O operator - iters (int): the number of iterations of the amplitude amplification subroutine - num_ref_wires (int): the number of wires used for the reflection - fixed_point (bool): whether to use the fixed-point amplitude amplification algorithm - - - Resources: - The resources are taken from the decomposition of :class:`qml.AmplitudeAmplification` class. - """ - gate_types = {} - ctrl = re.ResourceControlled.resource_rep( - base_class=O_op, - base_params=O_params, - num_ctrl_wires=1, - num_ctrl_values=0, - num_work_wires=0, - ) - phase_shift = re.ResourcePhaseShift.resource_rep() - hadamard = re.ResourceHadamard.resource_rep() - reflection = re.ResourceReflection.resource_rep( - base_class=U_op, base_params=U_params, num_ref_wires=num_ref_wires - ) - - if not fixed_point: - oracles = re.CompressedResourceOp(O_op, params=O_params) - gate_types[oracles] = iters - gate_types[reflection] = iters - - return gate_types - - iters = iters // 2 - - gate_types[ctrl] = iters * 2 - gate_types[phase_shift] = iters - gate_types[hadamard] = iters * 4 - gate_types[reflection] = iters - - return gate_types - - @property - def resource_params(self) -> Dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * U_op (Operator): the operator that prepares the state :math:`|\Psi\rangle` - * U_params (dict): the parameters for the U operator - * O_op (Operator): the oracle that flips the sign of the state :math:`|\phi\rangle` and does nothing to the state :math:`|\phi^{\perp}\rangle` - * O_params (dict): the parameters for the O operator - * iters (int): the number of iterations of the amplitude amplification subroutine - * num_ref_wires (int): the number of wires used for the reflection - * fixed_point (bool): whether to use the fixed-point amplitude amplification algorithm - """ - U_op = self.hyperparameters["U"] - O_op = self.hyperparameters["O"] - try: - U_params = U_op.resource_params - except (NotImplementedError, AttributeError): - U_params = {} - - try: - O_params = O_op.resource_params - except (NotImplementedError, AttributeError): - O_params = {} - - iters = self.hyperparameters["iters"] - fixed_point = self.hyperparameters["fixed_point"] - num_ref_wires = len(self.hyperparameters["reflection_wires"]) - - return { - "U_op": type(U_op), - "U_params": U_params, - "O_op": type(O_op), - "O_params": O_params, - "iters": iters, - "num_ref_wires": num_ref_wires, - "fixed_point": fixed_point, - } - - # pylint: disable=too-many-arguments - @classmethod - def resource_rep( - cls, U_op, U_params, O_op, O_params, iters, num_ref_wires, fixed_point - ) -> CompressedResourceOp: - r"""Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - U_op (Operator): the operator that prepares the state :math:`|\Psi\rangle` - U_params (dict): the parameters for the U operator - O_op (Operator): the oracle that flips the sign of the state :math:`|\phi\rangle` and does nothing to the state :math:`|\phi^{\perp}\rangle` - O_params (dict): the parameters for the O operator - iters (int): the number of iterations of the amplitude amplification subroutine - num_ref_wires (int): the number of wires used for the reflection - fixed_point (bool): whether to use the fixed-point amplitude amplification algorithm - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = { - "U_op": U_op, - "U_params": U_params, - "O_op": O_op, - "O_params": O_params, - "iters": iters, - "num_ref_wires": num_ref_wires, - "fixed_point": fixed_point, - } - return CompressedResourceOp(cls, params) diff --git a/pennylane/labs/resource_estimation/templates/trotter.py b/pennylane/labs/resource_estimation/templates/trotter.py deleted file mode 100644 index c8e7b3f0c12..00000000000 --- a/pennylane/labs/resource_estimation/templates/trotter.py +++ /dev/null @@ -1,494 +0,0 @@ -# Copyright 2025 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. -""" -Contains templates for Suzuki-Trotter approximation based subroutines. -""" -from collections import defaultdict -from functools import wraps -from typing import Dict - -import pennylane as qml -from pennylane.labs import resource_estimation as re -from pennylane.labs.resource_estimation import ( - CompressedResourceOp, - ResourceExp, - ResourceOperator, - ResourcesNotDefined, -) -from pennylane.templates import TrotterProduct -from pennylane.templates.subroutines.trotter import TrotterizedQfunc - -# pylint: disable=arguments-differ - - -class ResourceTrotterProduct( - TrotterProduct, ResourceOperator -): # pylint: disable=too-many-ancestors - r"""An operation representing the Suzuki-Trotter product approximation for the complex matrix - exponential of a given Hamiltonian. - - The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of - Hamiltonian expressed as a linear combination of terms which in general do not commute. Consider - the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`, the product formula is constructed using - symmetrized products of the terms in the Hamiltonian. The symmetrized products of order - :math:`m \in [1, 2, 4, ..., 2k]` with :math:`k \in \mathbb{N}` are given by: - - .. math:: - - \begin{align} - S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\ - S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\ - &\vdots \\ - S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2}, - \end{align} - - where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m`th order, - :math:`n`-step Suzuki-Trotter approximation is then defined as: - - .. math:: e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}. - - For more details see `J. Math. Phys. 32, 400 (1991) `_. - - Args: - hamiltonian (Union[.Hamiltonian, .Sum, .SProd]): The Hamiltonian written as a linear combination - of operators with known matrix exponentials. - time (float): The time of evolution, namely the parameter :math:`t` in :math:`e^{iHt}` - n (int): An integer representing the number of Trotter steps to perform - order (int): An integer (:math:`m`) representing the order of the approximation (must be 1 or even) - check_hermitian (bool): A flag to enable the validation check to ensure this is a valid unitary operator - - Resource Parameters: - * n (int): an integer representing the number of Trotter steps to perform - * order (int): an integer (:math:`m`) representing the order of the approximation (must be 1 or even) - * first_order_expansion (list[CompressedResourceOp]): A list of compressed operations corresponding to the exponentiated terms of the hamiltonian (:math:`e^{i t O_{j}}`). - - Resources: - The resources are defined according to the recursive formula presented above. Specifically, each - operator in the :code:`first_order_expansion` is called a number of times given by the formula: - - .. math:: C_{O_{j}} = 2n \cdot 5^{\frac{m}{2} - 1} - - Furthermore, the first and last terms of the Hamiltonian appear in pairs due to the symmetric form - of the recursive formula. Those counts are further simplified by grouping like terms as: - - .. math:: - - \begin{align} - C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\ - C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}. - \end{align} - - .. seealso:: :class:`~.TrotterProduct` - - **Example** - - The arguments can be provided directly to the :code:`resources()` function to extract the cost: - - >>> n, order = (1, 2) - >>> first_order_expansion = [re.ResourceRX.resource_rep(), re.ResourceRZ.resource_rep()] - >>> re.ResourceTrotterProduct.resources(n, order, first_order_expansion) - defaultdict(, {RX: 2, RZ: 1}) - - """ - - @staticmethod - def _resource_decomp( - n, order, first_order_expansion, **kwargs - ) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of - Hamiltonian expressed as a linear combination of terms which in general do not commute. Consider - the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`, the product formula is constructed using - symmetrized products of the terms in the Hamiltonian. The symmetrized products of order - :math:`m \in [1, 2, 4, ..., 2k]` with :math:`k \in \mathbb{N}` are given by: - - .. math:: - - \begin{align} - S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\ - S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\ - &\vdots \\ - S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2}, - \end{align} - - where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m`th order, - :math:`n`-step Suzuki-Trotter approximation is then defined as: - - .. math:: e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}. - - For more details see `J. Math. Phys. 32, 400 (1991) `_. - - Args: - n (int): an integer representing the number of Trotter steps to perform - order (int): an integer (:math:`m`) representing the order of the approximation (must be 1 or even) - first_order_expansion (list[CompressedResourceOp]): A list of compressed operations corresponding to the exponentiated terms of the hamiltonian (:math:`e^{i t O_{j}}`). - - Resources: - The resources are defined according to the recurrsive formula presented above. Specifically, each - operator in the :code:`first_order_expansion` is called a number of times given by the formula: - - .. math:: C_{O_{j}} = 2n \cdot 5^{\frac{m}{2} - 1} - - Furthermore, the first and last terms of the hamiltonian appear in pairs due to the symmetric form - of the recurrsive formula. Those counts are further simplified by grouping like terms as: - - .. math:: - - \begin{align} - C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\ - C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}. - \end{align} - - **Example** - - The arguments can be provided directly to the :code:`resources()` function to extract the cost: - - >>> n, order = (1, 2) - >>> first_order_expansion = [re.ResourceRX.resource_rep(), re.ResourceRZ.resource_rep()] - >>> re.ResourceTrotterProduct.resources(n, order, first_order_expansion) - defaultdict(, {RX: 2, RZ: 1}) - - """ - k = order // 2 - gate_types = defaultdict(int, {}) - - if order == 1: - for cp_rep in first_order_expansion: - gate_types[cp_rep] += n - return gate_types - - cp_rep_first = first_order_expansion[0] - cp_rep_last = first_order_expansion[-1] - cp_rep_rest = first_order_expansion[1:-1] - - for cp_rep in cp_rep_rest: - gate_types[cp_rep] += 2 * n * (5 ** (k - 1)) - - gate_types[cp_rep_first] += n * (5 ** (k - 1)) + 1 - gate_types[cp_rep_last] += n * (5 ** (k - 1)) - - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: A dictionary containing the resource parameters: - * n (int): an integer representing the number of Trotter steps to perform - * order (int): an integer (:math:`m`) representing the order of the approximation (must be 1 or even) - * first_order_expansion (list[CompressedResourceOp]): A list of compressed operations corresponding to the exponentiated terms of the hamiltonian (:math:`e^{i t O_{j}}`). - """ - n = self.hyperparameters["n"] - base = self.hyperparameters["base"] - order = self.hyperparameters["order"] - - first_order_expansion = [ - ResourceExp.resource_rep( - **re.ops.op_math.symbolic._extract_exp_params( # pylint: disable=protected-access - op, scalar=1j, num_steps=1 - ) - ) - for op in base.operands - ] - - return { - "n": n, - "order": order, - "first_order_expansion": first_order_expansion, - } - - @classmethod - def resource_rep(cls, n, order, first_order_expansion) -> CompressedResourceOp: - """Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - n (int): an integer representing the number of Trotter steps to perform - order (int): an integer (:math:`m`) representing the order of the approximation (must be 1 or even) - first_order_expansion (list[CompressedResourceOp]): A list of compressed operations corresponding to the exponentiated terms of the hamiltonian (:math:`e^{i t O_{j}}`). - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = { - "n": n, - "order": order, - "first_order_expansion": first_order_expansion, - } - return CompressedResourceOp(cls, params) - - @classmethod - def resources(cls, *args, **kwargs) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts.""" - return cls._resource_decomp(*args, **kwargs) - - -class ResourceTrotterizedQfunc(TrotterizedQfunc, ResourceOperator): - r"""Generates higher order Suzuki-Trotter product formulas from a set of - operations defined in a function. - - The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of - Hamiltonian expressed as a linear combination of terms which in general do not commute. Consider - the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`, the product formula is constructed using - symmetrized products of the terms in the Hamiltonian. The symmetrized products of order - :math:`m \in [1, 2, 4, ..., 2k]` with :math:`k \in \mathbb{N}` are given by: - - .. math:: - - \begin{align} - S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\ - S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\ - &\vdots \\ - S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2}, - \end{align} - - where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m`th order, - :math:`n`-step Suzuki-Trotter approximation is then defined as: - - .. math:: e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}. - - For more details see `J. Math. Phys. 32, 400 (1991) `_. - - Args: - time (float): the time of evolution, namely the parameter :math:`t` in :math:`e^{iHt}` - *trainable_args (tuple): the trainable arguments of the first-order expansion function - qfunc (Callable): the first-order expansion given as a callable function which queues operations - wires (Iterable): the set of wires the operation will act upon (should be identical to qfunc wires) - n (int): an integer representing the number of Trotter steps to perform - order (int): an integer (:math:`m`) representing the order of the approximation (must be 1 or even) - reverse (bool): if true, reverse the order of the operations queued by :code:`qfunc` - **non_trainable_kwargs (dict): non-trainable keyword arguments of the first-order expansion function - - Resource Parameters: - * n (int): an integer representing the number of Trotter steps to perform - * order (int): an integer (:math:`m`) representing the order of the approximation (must be 1 or even) - * qfunc_compressed_reps (list[CompressedResourceOp]): A list of compressed operations corresponding to the exponentiated terms of the hamiltonian (:math:`e^{i t O_{j}}`). - - Resources: - The resources are defined according to the recurrsive formula presented above. Specifically, each - operator in the :code:`first_order_expansion` is called a number of times given by the formula: - - .. math:: C_{O_{j}} = 2n \cdot 5^{\frac{m}{2} - 1} - - .. seealso:: :class:`~.TrotterizedQfunc` - - **Example** - - The arguments can be provided directly to the :code:`resources()` function to extract the cost: - - >>> n, order = (1, 2) - >>> first_order_expansion = [re.ResourceRX.resource_rep(), re.ResourceRZ.resource_rep()] - >>> re.ResourceTrotterizedQfunc.resources(n, order, first_order_expansion) - defaultdict(, {RX: 2, RZ: 2}) - - """ - - @staticmethod - def _resource_decomp( - n, order, qfunc_compressed_reps, **kwargs - ) -> Dict[CompressedResourceOp, int]: - r"""Returns a dictionary representing the resources of the operator. The - keys are the operators and the associated values are the counts. - - The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of - Hamiltonian expressed as a linear combination of terms which in general do not commute. Consider - the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`, the product formula is constructed using - symmetrized products of the terms in the Hamiltonian. The symmetrized products of order - :math:`m \in [1, 2, 4, ..., 2k]` with :math:`k \in \mathbb{N}` are given by: - - .. math:: - - \begin{align} - S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\ - S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\ - &\vdots \\ - S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2}, - \end{align} - - where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m`th order, - :math:`n`-step Suzuki-Trotter approximation is then defined as: - - .. math:: e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}. - - For more details see `J. Math. Phys. 32, 400 (1991) `_. - - Args: - n (int): an integer representing the number of Trotter steps to perform - order (int): an integer (:math:`m`) representing the order of the approximation (must be 1 or even) - qfunc_compressed_reps (list[CompressedResourceOp]): A list of compressed operations corresponding to the exponentiated terms of the hamiltonian (:math:`e^{i t O_{j}}`). - - Resources: - The resources are defined according to the recurrsive formula presented above. Specifically, each - operator in the :code:`first_order_expansion` is called a number of times given by the formula: - - .. math:: C_{O_{j}} = 2n \cdot 5^{\frac{m}{2} - 1} - - **Example** - - The arguments can be provided directly to the :code:`resources()` function to extract the cost: - - >>> n, order = (1, 2) - >>> first_order_expansion = [re.ResourceRX.resource_rep(), re.ResourceRZ.resource_rep()] - >>> re.ResourceTrotterizedQfunc.resources(n, order, first_order_expansion) - defaultdict(, {RX: 2, RZ: 2}) - - """ - k = order // 2 - gate_types = defaultdict(int, {}) - - if order == 1: - for cp_rep in qfunc_compressed_reps: - gate_types[cp_rep] += n - return gate_types - - for cp_rep in qfunc_compressed_reps: - gate_types[cp_rep] += 2 * n * (5 ** (k - 1)) - return gate_types - - @property - def resource_params(self) -> dict: - r"""Returns a dictionary containing the minimal information needed to compute the resources. - - Returns: - dict: dictionary containing the resource parameters - * n (int): an integer representing the number of Trotter steps to perform - * order (int): an integer (:math:`m`) representing the order of the approximation (must be 1 or even) - * qfunc_compressed_reps (list[CompressedResourceOp]): A list of compressed operations corresponding to the exponentiated terms of the hamiltonian (:math:`e^{i t O_{j}}`). - """ - with qml.QueuingManager.stop_recording(): - with qml.queuing.AnnotatedQueue() as q: - base_hyper_params = ("n", "order", "qfunc", "reverse") - - qfunc_args = self.parameters - qfunc_kwargs = { - k: v for k, v in self.hyperparameters.items() if not k in base_hyper_params - } - - qfunc = self.hyperparameters["qfunc"] - qfunc(*qfunc_args, wires=self.wires, **qfunc_kwargs) - - try: - qfunc_compressed_reps = tuple(op.resource_rep_from_op() for op in q.queue) - - except AttributeError as error: - raise ResourcesNotDefined( - "Every operation in the TrotterizedQfunc should be a ResourceOperator" - ) from error - - return { - "n": self.hyperparameters["n"], - "order": self.hyperparameters["order"], - "qfunc_compressed_reps": qfunc_compressed_reps, - } - - @classmethod - def resource_rep(cls, n, order, qfunc_compressed_reps, name=None) -> CompressedResourceOp: - """Returns a compressed representation containing only the parameters of - the Operator that are needed to compute a resource estimation. - - Args: - n (int): an integer representing the number of Trotter steps to perform - order (int): an integer (:math:`m`) representing the order of the approximation (must be 1 or even) - qfunc_compressed_reps (list[CompressedResourceOp]): A list of compressed operations corresponding to the exponentiated terms of the hamiltonian (:math:`e^{i t O_{j}}`). - - Returns: - CompressedResourceOp: the operator in a compressed representation - """ - params = { - "n": n, - "order": order, - "qfunc_compressed_reps": qfunc_compressed_reps, - } - return CompressedResourceOp(cls, params, name=name) - - def resource_rep_from_op(self) -> CompressedResourceOp: - r"""Returns a compressed representation directly from the operator""" - return self.__class__.resource_rep(**self.resource_params, name=self._name) - - -def resource_trotterize(qfunc, n=1, order=2, reverse=False): - r"""Generates higher order Suzuki-Trotter product formulas from a set of - operations defined in a function. - - The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of - Hamiltonian expressed as a linear combination of terms which in general do not commute. Consider - the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`, the product formula is constructed using - symmetrized products of the terms in the Hamiltonian. The symmetrized products of order - :math:`m \in [1, 2, 4, ..., 2k]` with :math:`k \in \mathbb{N}` are given by: - - .. math:: - - \begin{align} - S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\ - S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\ - &\vdots \\ - S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2}, - \end{align} - - where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m`th order, - :math:`n`-step Suzuki-Trotter approximation is then defined as: - - .. math:: e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}. - - For more details see `J. Math. Phys. 32, 400 (1991) `_. - - Args: - qfunc (Callable): A function which queues the operations corresponding to the exponentiated - terms of the hamiltonian (:math:`e^{i t O_{j}}`). The operations should be queued according - to the first order expression. - n (int): an integer representing the number of Trotter steps to perform - order (int): an integer (:math:`m`) representing the order of the approximation (must be 1 or even) - - Resources: - The resources are defined according to the recurrsive formula presented above. Specifically, each - operator in the :code:`first_order_expansion` is called a number of times given by the formula: - - .. math:: C_{O_{j}} = 2n \cdot 5^{\frac{m}{2} - 1} - - .. seealso:: :class:`~.trotterize` - - **Example** - - First we define a function which queues the first-order expansion: - - .. code-block:: python3 - - def first_order_expansion(time, theta, phi, wires): - "This is the first order expansion (U_1)." - re.ResourceRX(time*theta, wires[0]) - re.ResourceRY(time*phi, wires[1]) - - The arguments can be provided directly to the :code:`resources()` function to extract the cost: - - >>> n, order = (1, 2) - >>> time, theta, phi = (0.1, 0.2, 0.3) - >>> resource_op = re.resource_trotterize(first_order_expansion, n, order)(time, theta, phi, wires=['a', 'b']) - >>> resource_op.resources(**resource_op.resource_params) - defaultdict(, {RX: 2, RY: 2}) - - """ - - @wraps(qfunc) - def wrapper(*args, **kwargs): - time = args[0] - other_args = args[1:] - return ResourceTrotterizedQfunc( - time, *other_args, qfunc=qfunc, n=n, order=order, reverse=reverse, **kwargs - ) - - return wrapper From 4d7b994da2bf04c5b2a12a3e148c5f49628fef19 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Thu, 8 May 2025 11:57:39 -0400 Subject: [PATCH 4/4] clean repo --- pennylane/labs/resource_estimation/__init__.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pennylane/labs/resource_estimation/__init__.py b/pennylane/labs/resource_estimation/__init__.py index 7d70e810181..fc4f3a7f1d9 100644 --- a/pennylane/labs/resource_estimation/__init__.py +++ b/pennylane/labs/resource_estimation/__init__.py @@ -22,14 +22,4 @@ .. currentmodule:: pennylane.labs.resource_estimation -Resource Estimation Base Classes: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autosummary:: - :toctree: api - - ~ResourceOperator - """ - -from .resource_operator import ResourceOperator, ResourcesNotDefined