diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst index 05abd31e808..b7e58cb2279 100644 --- a/doc/development/deprecations.rst +++ b/doc/development/deprecations.rst @@ -45,6 +45,20 @@ Pending deprecations - Deprecated in v0.39 - Will be removed in v0.40 +* The ``decomp_depth`` argument in ``qml.device`` is deprecated. + + - Deprecated in v0.38 + - Will be removed in v0.39 + +* The ``simplify`` argument in ``qml.Hamiltonian`` and ``qml.ops.LinearCombination`` is deprecated. + Instead, ``qml.simplify()`` can be called on the constructed operator. + + - Deprecated in v0.37 + - Will be removed in v0.39 + +* The :class:`~pennylane.BasisStatePreparation` template is deprecated. + Instead, use :class:`~pennylane.BasisState`. + * The ``QubitStateVector`` template is deprecated. Instead, use ``StatePrep``. diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 7a4a716a1dd..95d4d17c4f2 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -44,6 +44,9 @@ `from pennylane.capture.primitives import *`. [(#6129)](https://github.com/PennyLaneAI/pennylane/pull/6129) +* `FermiWord` class now has a method to apply anti-commutator relations. + [(#6196)](https://github.com/PennyLaneAI/pennylane/pull/6196) + * `FermiWord` and `FermiSentence` classes now have methods to compute adjoints. [(#6166)](https://github.com/PennyLaneAI/pennylane/pull/6166) @@ -108,6 +111,10 @@

Deprecations 👋

+* The `qml.BasisStatePreparation` template is deprecated. + Instead, use `qml.BasisState`. + [(#6021)](https://github.com/PennyLaneAI/pennylane/pull/6021) + * The `'ancilla'` argument for `qml.iterative_qpe` has been deprecated. Instead, use the `'aux_wire'` argument. [(#6277)](https://github.com/PennyLaneAI/pennylane/pull/6277) diff --git a/pennylane/devices/tests/test_templates.py b/pennylane/devices/tests/test_templates.py index 86e1101340a..c91304488ef 100644 --- a/pennylane/devices/tests/test_templates.py +++ b/pennylane/devices/tests/test_templates.py @@ -225,6 +225,9 @@ def circuit(): [math.fidelity_statevector(circuit(), exp_state)], [1.0], atol=tol(dev.shots) ) + @pytest.mark.filterwarnings( + "ignore:BasisStatePreparation is deprecated:pennylane.PennyLaneDeprecationWarning" + ) def test_BasisStatePreparation(self, device, tol): """Test the BasisStatePreparation template.""" dev = device(4) diff --git a/pennylane/fermi/fermionic.py b/pennylane/fermi/fermionic.py index 193cc0b4be8..b5f03020780 100644 --- a/pennylane/fermi/fermionic.py +++ b/pennylane/fermi/fermionic.py @@ -324,6 +324,110 @@ def to_mat(self, n_orbitals=None, format="dense", buffer_size=None): wire_order=list(range(largest_order)), format=format, buffer_size=buffer_size ) + def shift_operator(self, initial_position, final_position): + r"""Shifts an operator in the FermiWord from ``initial_position`` to ``final_position`` by applying the fermionic anti-commutation relations. + + There are three `anti-commutator relations `_: + + .. math:: + \left\{ a_i, a_j \right\} = 0, \quad \left\{ a^{\dagger}_i, a^{\dagger}_j \right\} = 0, \quad \left\{ a_i, a^{\dagger}_j \right\} = \delta_{ij}, + + + where + + .. math:: + \left\{a_i, a_j \right\} = a_i a_j + a_j a_i, + + and + + .. math:: + \delta_{ij} = \begin{cases} 1 & i = j \\ 0 & i \neq j \end{cases}. + + Args: + initial_position (int): The position of the operator to be shifted. + final_position (int): The desired position of the operator. + + Returns: + FermiSentence: The ``FermiSentence`` obtained after applying the anti-commutator relations. + + Raises: + TypeError: if ``initial_position`` or ``final_position`` is not an integer + ValueError: if ``initial_position`` or ``final_position`` are outside the range ``[0, len(fermiword) - 1]`` + where ``len(fermiword)`` is the number of operators in the FermiWord. + + + **Example** + + >>> w = qml.fermi.FermiWord({(0, 0): '+', (1, 1): '-'}) + >>> w.shift_operator(0, 1) + -1 * a(1) a⁺(0) + """ + + if not isinstance(initial_position, int) or not isinstance(final_position, int): + raise TypeError("Positions must be integers.") + + if initial_position < 0 or final_position < 0: + raise ValueError("Positions must be positive integers.") + + if initial_position > len(self.sorted_dic) - 1 or final_position > len(self.sorted_dic) - 1: + raise ValueError("Positions are out of range.") + + if initial_position == final_position: + return FermiSentence({self: 1}) + + fw = self + fs = FermiSentence({fw: 1}) + delta = 1 if initial_position < final_position else -1 + current = initial_position + + while current != final_position: + indices = list(fw.sorted_dic.keys()) + next = current + delta + curr_idx, curr_val = indices[current], fw[indices[current]] + next_idx, next_val = indices[next], fw[indices[next]] + + # commuting identical terms + if curr_idx[1] == next_idx[1] and curr_val == next_val: + current += delta + continue + + coeff = fs.pop(fw) + + fw = dict(fw) + fw[(current, next_idx[1])] = next_val + fw[(next, curr_idx[1])] = curr_val + + if curr_idx[1] != next_idx[1]: + del fw[curr_idx], fw[next_idx] + + fw = FermiWord(fw) + + # anti-commutator is 0 + if curr_val == next_val or curr_idx[1] != next_idx[1]: + current += delta + fs += -coeff * fw + continue + + # anti-commutator is 1 + _min = min(current, next) + _max = max(current, next) + items = list(fw.sorted_dic.items()) + + left = FermiWord({(i, key[1]): value for i, (key, value) in enumerate(items[:_min])}) + middle = FermiWord( + {(i, key[1]): value for i, (key, value) in enumerate(items[_min : _max + 1])} + ) + right = FermiWord( + {(i, key[1]): value for i, (key, value) in enumerate(items[_max + 1 :])} + ) + + terms = left * (1 - middle) * right + fs += coeff * terms + + current += delta + + return fs + # pylint: disable=useless-super-delegation class FermiSentence(dict): diff --git a/pennylane/templates/state_preparations/basis.py b/pennylane/templates/state_preparations/basis.py index 48211a6d1e8..d63fa0f1b32 100644 --- a/pennylane/templates/state_preparations/basis.py +++ b/pennylane/templates/state_preparations/basis.py @@ -15,6 +15,8 @@ Contains the BasisStatePreparation template. """ +import warnings + import numpy as np import pennylane as qml @@ -30,6 +32,8 @@ class BasisStatePreparation(Operation): ``basis_state`` influences the circuit architecture and is therefore incompatible with gradient computations. + ``BasisStatePreparation`` is deprecated and will be removed in version 0.40. Instead, please use ``BasisState``. + Args: basis_state (array): Input array of shape ``(n,)``, where n is the number of wires the state preparation acts on. @@ -59,6 +63,13 @@ def circuit(basis_state): ndim_params = (1,) def __init__(self, basis_state, wires, id=None): + + warnings.warn( + "BasisStatePreparation is deprecated and will be removed in version 0.40. " + "Instead, please use BasisState.", + qml.PennyLaneDeprecationWarning, + ) + basis_state = qml.math.stack(basis_state) # check if the `basis_state` param is batched diff --git a/tests/capture/test_templates.py b/tests/capture/test_templates.py index d04990b2971..619723a9b02 100644 --- a/tests/capture/test_templates.py +++ b/tests/capture/test_templates.py @@ -27,8 +27,12 @@ jax = pytest.importorskip("jax") jnp = jax.numpy -pytestmark = pytest.mark.jax - +pytestmark = [ + pytest.mark.jax, + pytest.mark.filterwarnings( + "ignore:BasisStatePreparation is deprecated:pennylane.PennyLaneDeprecationWarning" + ), +] original_op_bind_code = qml.operation.Operator._primitive_bind_call.__code__ diff --git a/tests/fermi/test_fermionic.py b/tests/fermi/test_fermionic.py index dc61295115a..4e028ad9875 100644 --- a/tests/fermi/test_fermionic.py +++ b/tests/fermi/test_fermionic.py @@ -53,6 +53,62 @@ fw7 = FermiWord({(0, 10): "-", (1, 30): "+", (2, 0): "-", (3, 400): "+"}) fw7_dag = FermiWord({(0, 400): "-", (1, 0): "+", (2, 30): "-", (3, 10): "+"}) +fw8 = FermiWord({(0, 0): "-", (1, 1): "+"}) +fw8c = FermiWord({(0, 1): "+", (1, 0): "-"}) +fw8cs = FermiSentence({fw8c: -1}) + +fw9 = FermiWord({(0, 0): "-", (1, 1): "-"}) +fw9c = FermiWord({(0, 1): "-", (1, 0): "-"}) +fw9cs = FermiSentence({fw9c: -1}) + +fw10 = FermiWord({(0, 0): "+", (1, 1): "+"}) +fw10c = FermiWord({(0, 1): "+", (1, 0): "+"}) +fw10cs = FermiSentence({fw10c: -1}) + +fw11 = FermiWord({(0, 0): "-", (1, 0): "+"}) +fw11c = FermiWord({(0, 0): "+", (1, 0): "-"}) +fw11cs = 1 + FermiSentence({fw11c: -1}) + +fw12 = FermiWord({(0, 0): "+", (1, 0): "+"}) +fw12c = FermiWord({(0, 0): "+", (1, 0): "+"}) +fw12cs = FermiSentence({fw12c: 1}) + +fw13 = FermiWord({(0, 0): "-", (1, 0): "-"}) +fw13c = FermiWord({(0, 0): "-", (1, 0): "-"}) +fw13cs = FermiSentence({fw13c: 1}) + +fw14 = FermiWord({(0, 0): "+", (1, 0): "-"}) +fw14c = FermiWord({(0, 0): "-", (1, 0): "+"}) +fw14cs = 1 + FermiSentence({fw14c: -1}) + +fw15 = FermiWord({(0, 0): "-", (1, 1): "+", (2, 2): "+"}) +fw15c = FermiWord({(0, 1): "+", (1, 0): "-", (2, 2): "+"}) +fw15cs = FermiSentence({fw15c: -1}) + +fw16 = FermiWord({(0, 0): "-", (1, 1): "+", (2, 2): "-"}) +fw16c = FermiWord({(0, 0): "-", (1, 2): "-", (2, 1): "+"}) +fw16cs = FermiSentence({fw16c: -1}) + +fw17 = FermiWord({(0, 0): "-", (1, 0): "+", (2, 2): "-"}) +fw17c1 = FermiWord({(0, 2): "-"}) +fw17c2 = FermiWord({(0, 0): "+", (1, 0): "-", (2, 2): "-"}) +fw17cs = fw17c1 - fw17c2 + +fw18 = FermiWord({(0, 0): "+", (1, 1): "+", (2, 2): "-", (3, 3): "-"}) +fw18c = FermiWord({(0, 0): "+", (1, 3): "-", (2, 1): "+", (3, 2): "-"}) +fw18cs = FermiSentence({fw18c: 1}) + +fw19 = FermiWord({(0, 0): "+", (1, 1): "+", (2, 2): "-", (3, 2): "+"}) +fw19c1 = FermiWord({(0, 0): "+", (1, 1): "+"}) +fw19c2 = FermiWord({(0, 2): "+", (1, 0): "+", (2, 1): "+", (3, 2): "-"}) +fw19cs = FermiSentence({fw19c1: 1, fw19c2: -1}) + +fw20 = FermiWord({(0, 0): "-", (1, 0): "+", (2, 1): "-", (3, 0): "-", (4, 0): "+"}) +fw20c1 = FermiWord({(0, 0): "-", (1, 0): "+", (2, 1): "-"}) +fw20c2 = FermiWord({(0, 0): "+", (1, 1): "-", (2, 0): "-"}) +fw20c3 = FermiWord({(0, 0): "+", (1, 0): "-", (2, 0): "+", (3, 1): "-", (4, 0): "-"}) +fw20cs = fw20c1 + fw20c2 - fw20c3 + class TestFermiWord: def test_missing(self): @@ -167,6 +223,41 @@ def test_to_mat_error(self): with pytest.raises(ValueError, match="n_orbitals cannot be smaller than 2"): fw1.to_mat(n_orbitals=1) + tup_fw_shift = ( + (fw8, 0, 1, fw8cs), + (fw9, 0, 1, fw9cs), + (fw10, 0, 1, fw10cs), + (fw11, 0, 1, fw11cs), + (fw12, 0, 1, fw12cs), + (fw13, 0, 1, fw13cs), + (fw14, 0, 1, fw14cs), + (fw15, 0, 1, fw15cs), + (fw16, 1, 2, fw16cs), + (fw17, 0, 1, fw17cs), + (fw8, 0, 0, FermiSentence({fw8: 1})), + (fw8, 1, 0, fw8cs), + (fw11, 1, 0, fw11cs), + (fw18, 3, 1, fw18cs), + (fw19, 3, 0, fw19cs), + (fw20, 4, 0, fw20cs), + ) + + @pytest.mark.parametrize("fw, i, j, fs", tup_fw_shift) + def test_shift_operator(self, fw, i, j, fs): + """Test that the shift_operator method correctly applies the anti-commutator relations.""" + assert fw.shift_operator(i, j) == fs + + def test_shift_operator_errors(self): + """Test that the shift_operator method correctly raises exceptions.""" + with pytest.raises(TypeError, match="Positions must be integers."): + fw8.shift_operator(0.5, 1) + + with pytest.raises(ValueError, match="Positions must be positive integers."): + fw8.shift_operator(-1, 0) + + with pytest.raises(ValueError, match="Positions are out of range."): + fw8.shift_operator(1, 2) + tup_fw_dag = ( (fw1, fw1_dag), (fw2, fw2_dag), @@ -588,6 +679,15 @@ def test_array_must_not_exceed_length_1(self, method_name): } ) +fs8 = fw8 + fw9 +fs8c = fw8 + fw9cs + +fs9 = 1.3 * fw8 + (1.4 + 3.8j) * fw9 +fs9c = 1.3 * fw8 + (1.4 + 3.8j) * fw9cs + +fs10 = -1.3 * fw11 + 2.3 * fw9 +fs10c = -1.3 * fw11cs + 2.3 * fw9 + class TestFermiSentence: def test_missing(self): diff --git a/tests/templates/test_state_preparations/test_basis_state_prep.py b/tests/templates/test_state_preparations/test_basis_state_prep.py index 90b88463a05..441af8e007d 100644 --- a/tests/templates/test_state_preparations/test_basis_state_prep.py +++ b/tests/templates/test_state_preparations/test_basis_state_prep.py @@ -21,6 +21,10 @@ import pennylane as qml +pytestmark = pytest.mark.filterwarnings( + "ignore:BasisStatePreparation is deprecated:pennylane.PennyLaneDeprecationWarning" +) + def test_standard_validity(): """Check the operation using the assert_valid function.""" @@ -33,6 +37,12 @@ def test_standard_validity(): qml.ops.functions.assert_valid(op) +def test_BasisStatePreparation_is_deprecated(): + """Test that BasisStatePreparation is deprecated.""" + with pytest.warns(qml.PennyLaneDeprecationWarning, match="BasisStatePreparation is deprecated"): + _ = qml.BasisStatePreparation([1, 0], wires=[0, 1]) + + class TestDecomposition: """Tests that the template defines the correct decomposition.""" diff --git a/tests/test_qnode.py b/tests/test_qnode.py index 1322ca62c16..19ce1b067a6 100644 --- a/tests/test_qnode.py +++ b/tests/test_qnode.py @@ -975,7 +975,7 @@ def test_sampling_with_mcm(self, basis_state, mocker): @qml.qnode(dev) def cry_qnode(x): """QNode where we apply a controlled Y-rotation.""" - qml.BasisStatePreparation(basis_state, wires=[0, 1]) + qml.BasisState(basis_state, wires=[0, 1]) qml.CRY(x, wires=[0, 1]) return qml.sample(qml.PauliZ(1)) @@ -983,7 +983,7 @@ def cry_qnode(x): def conditional_ry_qnode(x): """QNode where the defer measurements transform is applied by default under the hood.""" - qml.BasisStatePreparation(basis_state, wires=[0, 1]) + qml.BasisState(basis_state, wires=[0, 1]) m_0 = qml.measure(0) qml.cond(m_0, qml.RY)(x, wires=1) return qml.sample(qml.PauliZ(1)) diff --git a/tests/test_qnode_legacy.py b/tests/test_qnode_legacy.py index 73eaf29b302..681be8161fe 100644 --- a/tests/test_qnode_legacy.py +++ b/tests/test_qnode_legacy.py @@ -890,7 +890,7 @@ def test_sampling_with_mcm(self, basis_state, mocker): @qml.qnode(dev) def cry_qnode(x): """QNode where we apply a controlled Y-rotation.""" - qml.BasisStatePreparation(basis_state, wires=[0, 1]) + qml.BasisState(basis_state, wires=[0, 1]) qml.CRY(x, wires=[0, 1]) return qml.sample(qml.PauliZ(1)) @@ -898,7 +898,7 @@ def cry_qnode(x): def conditional_ry_qnode(x): """QNode where the defer measurements transform is applied by default under the hood.""" - qml.BasisStatePreparation(basis_state, wires=[0, 1]) + qml.BasisState(basis_state, wires=[0, 1]) m_0 = qml.measure(0) qml.cond(m_0, qml.RY)(x, wires=1) return qml.sample(qml.PauliZ(1)) diff --git a/tests/transforms/test_batch_input.py b/tests/transforms/test_batch_input.py index 3d97e981ed2..2499aafdcdd 100644 --- a/tests/transforms/test_batch_input.py +++ b/tests/transforms/test_batch_input.py @@ -200,6 +200,9 @@ def circuit2(data, weights): assert np.allclose(res, indiv_res) +@pytest.mark.filterwarnings( + "ignore:BasisStatePreparation is deprecated:pennylane.PennyLaneDeprecationWarning" +) def test_basis_state_preparation(mocker): """Test that batching works for BasisStatePreparation""" dev = qml.device("default.qubit", wires=3) diff --git a/tests/transforms/test_batch_params.py b/tests/transforms/test_batch_params.py index ad4daddeb69..29b89ebda4a 100644 --- a/tests/transforms/test_batch_params.py +++ b/tests/transforms/test_batch_params.py @@ -174,6 +174,9 @@ def circuit2(data, weights): assert np.allclose(res, indiv_res) +@pytest.mark.filterwarnings( + "ignore:BasisStatePreparation is deprecated:pennylane.PennyLaneDeprecationWarning" +) def test_basis_state_preparation(mocker): """Test that batching works for BasisStatePreparation""" dev = qml.device("default.qubit", wires=4) diff --git a/tests/transforms/test_defer_measurements.py b/tests/transforms/test_defer_measurements.py index 3d5e98791ea..52a65f924a5 100644 --- a/tests/transforms/test_defer_measurements.py +++ b/tests/transforms/test_defer_measurements.py @@ -1347,6 +1347,9 @@ def quantum_control_circuit(rads): class TestTemplates: """Tests templates being conditioned on mid-circuit measurement outcomes.""" + @pytest.mark.filterwarnings( + "ignore:BasisStatePreparation is deprecated:pennylane.PennyLaneDeprecationWarning" + ) def test_basis_state_prep(self): """Test the basis state prep template conditioned on mid-circuit measurement outcomes."""