From 828bca67744e3d71d5fd8918625ac52418e86ca1 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Fri, 10 Jan 2025 09:51:33 +0000 Subject: [PATCH 1/2] [no ci] bump nightly version --- pennylane/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/_version.py b/pennylane/_version.py index ec55ad0905d..15db8ba2cd5 100644 --- a/pennylane/_version.py +++ b/pennylane/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.41.0-dev3" +__version__ = "0.41.0-dev4" From cc6849ad1135e465c1bedb9cb29c0504260cb7f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 17:54:51 +0000 Subject: [PATCH 2/2] Daily rc sync to master (#6806) Co-authored-by: PietropaoloFrisoni Co-authored-by: Christina Lee Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Mudit Pandey Co-authored-by: ringo-but-quantum Co-authored-by: Andrija Paurevic <46359773+andrijapau@users.noreply.github.com> Co-authored-by: Austin Huang <65315367+austingmhuang@users.noreply.github.com> Co-authored-by: GitHub Actions Bot <> --- .github/workflows/tests.yml | 2 + doc/code/qml_fermi.rst | 8 +-- doc/releases/changelog-0.40.0.md | 10 ++- pennylane/__init__.py | 2 + pennylane/bose/bosonic.py | 20 +++--- pennylane/bose/bosonic_mapping.py | 10 +-- pennylane/devices/_legacy_device.py | 5 +- pennylane/fermi/fermionic.py | 24 ++++--- .../labs/vibrational/christiansen_utils.py | 70 +++++++++---------- pennylane/ops/functions/equal.py | 33 --------- pennylane/qchem/convert_openfermion.py | 10 +-- pennylane/qchem/observable_hf.py | 10 +-- pennylane/qchem/structure.py | 4 +- .../templates/subroutines/basis_rotation.py | 14 ++-- tests/bose/test_binary_mapping.py | 2 +- tests/bose/test_christiansen_mapping.py | 2 +- tests/bose/test_unary_mapping.py | 2 +- tests/devices/test_legacy_device.py | 14 ++++ tests/ops/functions/test_equal.py | 4 +- .../test_convert_openfermion.py | 28 ++++---- .../test_subroutines/test_basis_rotation.py | 6 +- 21 files changed, 134 insertions(+), 146 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e0bfa8d1fe7..63c8074e524 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,6 +9,8 @@ on: - reopened - synchronize - ready_for_review + branches-ignore: + - 'v[0-9]+.[0-9]+.[0-9]+-docs' # Scheduled trigger on Monday at 2:47am UTC schedule: - cron: "47 2 * * 1" diff --git a/doc/code/qml_fermi.rst b/doc/code/qml_fermi.rst index 2c528c1f2fe..b6c9d1b6a57 100644 --- a/doc/code/qml_fermi.rst +++ b/doc/code/qml_fermi.rst @@ -78,15 +78,15 @@ the orbital it acts on. The values of the dictionary are one of ``'+'`` or ``'-' denote creation and annihilation operators, respectively. The operator :math:`a^{\dagger}_0 a_3 a^{\dagger}_1` can then be constructed with ->>> qml.fermi.FermiWord({(0, 0): '+', (1, 3): '-', (2, 1): '+'}) +>>> qml.FermiWord({(0, 0): '+', (1, 3): '-', (2, 1): '+'}) a⁺(0) a(3) a⁺(1) A Fermi sentence can be constructed directly by passing a dictionary of Fermi words and their corresponding coefficients to the :class:`~pennylane.fermi.FermiSentence` class. For instance, the Fermi sentence :math:`1.2 a^{\dagger}_0 a_0 + 2.3 a^{\dagger}_3 a_3` can be constructed as ->>> fw1 = qml.fermi.FermiWord({(0, 0): '+', (1, 0): '-'}) ->>> fw2 = qml.fermi.FermiWord({(0, 3): '+', (1, 3): '-'}) ->>> qml.fermi.FermiSentence({fw1: 1.2, fw2: 2.3}) +>>> fw1 = qml.FermiWord({(0, 0): '+', (1, 0): '-'}) +>>> fw2 = qml.FermiWord({(0, 3): '+', (1, 3): '-'}) +>>> qml.FermiSentence({fw1: 1.2, fw2: 2.3}) 1.2 * a⁺(0) a(0) + 2.3 * a⁺(3) a(3) diff --git a/doc/releases/changelog-0.40.0.md b/doc/releases/changelog-0.40.0.md index e3769326619..7d9707882e6 100644 --- a/doc/releases/changelog-0.40.0.md +++ b/doc/releases/changelog-0.40.0.md @@ -111,9 +111,6 @@ * Added support to build a vibrational Hamiltonian in Taylor form. [(#6523)](https://github.com/PennyLaneAI/pennylane/pull/6523) -* Added support to build a vibrational Hamiltonian in the Christiansen form. - [(#6560)](https://github.com/PennyLaneAI/pennylane/pull/6560) -

Improvements 🛠

QChem improvements

@@ -389,6 +386,7 @@ * `qml.BasisRotation` template is now JIT compatible. [(#6019)](https://github.com/PennyLaneAI/pennylane/pull/6019) + [(#6779)](https://github.com/PennyLaneAI/pennylane/pull/6779) * The Jaxpr primitives for `for_loop`, `while_loop` and `cond` now store slices instead of numbers of args. @@ -493,6 +491,12 @@ such as `shots`, `rng` and `prng_key`. for the horizontal Cartan subalgebra instead of `$\mathfrak{h}$`. [(#6747)](https://github.com/PennyLaneAI/pennylane/pull/6747) +

Construct vibrational Hamiltonians 🫨

+ +* Added support to build a vibrational Hamiltonian in the Christiansen form. + [(#6560)](https://github.com/PennyLaneAI/pennylane/pull/6560) + [(#6792)](https://github.com/PennyLaneAI/pennylane/pull/6792) +

Breaking changes 💔

* The default graph coloring method of `qml.dot`, `qml.sum`, and `qml.pauli.optimize_measurements` for grouping observables was changed diff --git a/pennylane/__init__.py b/pennylane/__init__.py index 3d1c0f1b911..6e8a8fe903b 100644 --- a/pennylane/__init__.py +++ b/pennylane/__init__.py @@ -35,6 +35,8 @@ from pennylane.fermi import ( FermiC, FermiA, + FermiWord, + FermiSentence, jordan_wigner, parity_transform, bravyi_kitaev, diff --git a/pennylane/bose/bosonic.py b/pennylane/bose/bosonic.py index 82da7b39fbb..cb09a1a1143 100644 --- a/pennylane/bose/bosonic.py +++ b/pennylane/bose/bosonic.py @@ -30,7 +30,7 @@ class BoseWord(dict): symbols that denote creation and annihilation operators, respectively. The operator :math:`b^{\dagger}_0 b_1` can then be constructed as - >>> w = qml.bose.BoseWord({(0, 0) : '+', (1, 1) : '-'}) + >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> print(w) b⁺(0) b(1) """ @@ -112,7 +112,7 @@ def to_string(self): represented by the number of the wire it operates on, and a `+` or `-` to indicate either a creation or annihilation operator. - >>> w = qml.bose.BoseWord({(0, 0) : '+', (1, 1) : '-'}) + >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> w.to_string() 'b⁺(0) b(1)' """ @@ -209,7 +209,7 @@ def __rsub__(self, other): def __mul__(self, other): r"""Multiply a BoseWord with another BoseWord, a BoseSentence, or a constant. - >>> w = qml.bose.BoseWord({(0, 0) : '+', (1, 1) : '-'}) + >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> print(w * w) b⁺(0) b(1) b⁺(0) b(1) """ @@ -263,7 +263,7 @@ def __rmul__(self, other): def __pow__(self, value): r"""Exponentiate a Bose word to an integer power. - >>> w = qml.bose.BoseWord({(0, 0) : '+', (1, 1) : '-'}) + >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> print(w**3) b⁺(0) b(1) b⁺(0) b(1) b⁺(0) b(1) """ @@ -280,7 +280,7 @@ def __pow__(self, value): def normal_order(self): r"""Convert a BoseWord to its normal-ordered form. - >>> bw = qml.bose.BoseWord({(0, 0): "-", (1, 0): "-", (2, 0): "+", (3, 0): "+"}) + >>> bw = qml.BoseWord({(0, 0): "-", (1, 0): "-", (2, 0): "+", (3, 0): "+"}) >>> print(bw.normal_order()) 4.0 * b⁺(0) b(0) + 2.0 * I @@ -420,9 +420,9 @@ class BoseSentence(dict): r"""Dictionary used to represent a Bose sentence, a linear combination of Bose words, with the keys as BoseWord instances and the values correspond to coefficients. - >>> w1 = qml.bose.BoseWord({(0, 0) : '+', (1, 1) : '-'}) - >>> w2 = qml.bose.BoseWord({(0, 1) : '+', (1, 2) : '-'}) - >>> s = BoseSentence({w1 : 1.2, w2: 3.1}) + >>> w1 = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) + >>> w2 = qml.BoseWord({(0, 1) : '+', (1, 2) : '-'}) + >>> s = qml.BoseSentence({w1 : 1.2, w2: 3.1}) >>> print(s) 1.2 * b⁺(0) b(1) + 3.1 * b⁺(1) b(2) @@ -608,8 +608,8 @@ def simplify(self, tol=1e-8): def normal_order(self): r"""Convert a BoseSentence to its normal-ordered form. - >>> bw = qml.bose.BoseWord({(0, 0): "-", (1, 0): "-", (2, 0): "+", (3, 0): "+"}) - >>> bs = qml.bose.BoseSentence({bw: 1}) + >>> bw = qml.BoseWord({(0, 0): "-", (1, 0): "-", (2, 0): "+", (3, 0): "+"}) + >>> bs = qml.BoseSentence({bw: 1}) >>> print(bw.normal_order()) 4.0 * b⁺(0) b(0) + 2.0 * I diff --git a/pennylane/bose/bosonic_mapping.py b/pennylane/bose/bosonic_mapping.py index 5c5eb55341d..87112648b8e 100644 --- a/pennylane/bose/bosonic_mapping.py +++ b/pennylane/bose/bosonic_mapping.py @@ -65,7 +65,7 @@ def binary_mapping( **Example** - >>> w = qml.bose.BoseWord({(0, 0): "+"}) + >>> w = qml.BoseWord({(0, 0): "+"}) >>> qml.binary_mapping(w, n_states=4) ( 0.6830127018922193 * X(0) @@ -95,7 +95,7 @@ def binary_mapping( @singledispatch def _binary_mapping_dispatch(bose_operator, n_states, tol): """Dispatches to appropriate function if bose_operator is a BoseWord or BoseSentence.""" - raise ValueError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}") + raise TypeError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}") @_binary_mapping_dispatch.register @@ -190,7 +190,7 @@ def unary_mapping( **Example** - >>> w = qml.bose.BoseWord({(0, 0): "+"}) + >>> w = qml.BoseWord({(0, 0): "+"}) >>> qml.unary_mapping(w, n_states=4) ( 0.25 * X(0) @ X(1) @@ -224,7 +224,7 @@ def unary_mapping( @singledispatch def _unary_mapping_dispatch(bose_operator, n_states, ps=False, wires_map=None, tol=None): """Dispatches to appropriate function if bose_operator is a BoseWord or BoseSentence.""" - raise ValueError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}") + raise TypeError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}") @_unary_mapping_dispatch.register @@ -367,7 +367,7 @@ def christiansen_mapping( @singledispatch def _christiansen_mapping_dispatch(bose_operator, tol): """Dispatches to appropriate function if bose_operator is a BoseWord or BoseSentence.""" - raise ValueError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}") + raise TypeError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}") @_christiansen_mapping_dispatch.register diff --git a/pennylane/devices/_legacy_device.py b/pennylane/devices/_legacy_device.py index 93342f9d2e2..33400c37695 100644 --- a/pennylane/devices/_legacy_device.py +++ b/pennylane/devices/_legacy_device.py @@ -813,7 +813,10 @@ def _all_multi_term_obs_supported(self, circuit): # Some measurements are not observable based. continue - if mp.obs.name == "LinearCombination" and not self.supports_observable("Hamiltonian"): + if mp.obs.name == "LinearCombination" and not ( + self.supports_observable("Hamiltonian") + or self.supports_observable("LinearCombination") + ): return False if mp.obs.name in ( diff --git a/pennylane/fermi/fermionic.py b/pennylane/fermi/fermionic.py index 6aacbdabe96..f4162794176 100644 --- a/pennylane/fermi/fermionic.py +++ b/pennylane/fermi/fermionic.py @@ -30,7 +30,7 @@ class FermiWord(dict): :math:`a^{\dagger}_0 a_1` can then be constructed as >>> w = FermiWord({(0, 0) : '+', (1, 1) : '-'}) - >>> w + >>> print(w) a⁺(0) a(1) """ @@ -114,7 +114,7 @@ def to_string(self): >>> w = FermiWord({(0, 0) : '+', (1, 1) : '-'}) >>> w.to_string() - a⁺(0) a(1) + 'a⁺(0) a(1)' """ if len(self) == 0: return "I" @@ -211,7 +211,7 @@ def __mul__(self, other): r"""Multiply a FermiWord with another FermiWord, a FermiSentence, or a constant. >>> w = FermiWord({(0, 0) : '+', (1, 1) : '-'}) - >>> w * w + >>> print(w * w) a⁺(0) a(1) a⁺(0) a(1) """ @@ -265,7 +265,7 @@ def __pow__(self, value): r"""Exponentiate a Fermi word to an integer power. >>> w = FermiWord({(0, 0) : '+', (1, 1) : '-'}) - >>> w**3 + >>> print(w**3) a⁺(0) a(1) a⁺(0) a(1) a⁺(0) a(1) """ @@ -348,7 +348,7 @@ def shift_operator(self, initial_position, final_position): **Example** - >>> w = qml.fermi.FermiWord({(0, 0): '+', (1, 1): '-'}) + >>> w = qml.FermiWord({(0, 0): '+', (1, 1): '-'}) >>> w.shift_operator(0, 1) -1 * a(1) a⁺(0) """ @@ -427,7 +427,7 @@ class FermiSentence(dict): >>> w1 = FermiWord({(0, 0) : '+', (1, 1) : '-'}) >>> w2 = FermiWord({(0, 1) : '+', (1, 2) : '-'}) >>> s = FermiSentence({w1 : 1.2, w2: 3.1}) - >>> s + >>> print(s) 1.2 * a⁺(0) a(1) + 3.1 * a⁺(1) a(2) """ @@ -754,13 +754,15 @@ class FermiC(FermiWord): To construct the operator :math:`a^{\dagger}_0`: - >>> FermiC(0) + >>> w = FermiC(0) + >>> print(w) a⁺(0) This can be combined with the annihilation operator :class:`~pennylane.FermiA`. For example, :math:`a^{\dagger}_0 a_1 a^{\dagger}_2 a_3` can be constructed as: - >>> qml.FermiC(0) * qml.FermiA(1) * qml.FermiC(2) * qml.FermiA(3) + >>> w = qml.FermiC(0) * qml.FermiA(1) * qml.FermiC(2) * qml.FermiA(3) + >>> print(w) a⁺(0) a(1) a⁺(2) a(3) """ @@ -796,13 +798,15 @@ class FermiA(FermiWord): To construct the operator :math:`a_0`: - >>> FermiA(0) + >>> w = FermiA(0) + >>> print(w) a(0) This can be combined with the creation operator :class:`~pennylane.FermiC`. For example, :math:`a^{\dagger}_0 a_1 a^{\dagger}_2 a_3` can be constructed as: - >>> qml.FermiC(0) * qml.FermiA(1) * qml.FermiC(2) * qml.FermiA(3) + >>> w = qml.FermiC(0) * qml.FermiA(1) * qml.FermiC(2) * qml.FermiA(3) + >>> print(w) a⁺(0) a(1) a⁺(2) a(3) """ diff --git a/pennylane/labs/vibrational/christiansen_utils.py b/pennylane/labs/vibrational/christiansen_utils.py index 93ef5c7e275..33a6f26ed2f 100644 --- a/pennylane/labs/vibrational/christiansen_utils.py +++ b/pennylane/labs/vibrational/christiansen_utils.py @@ -13,7 +13,7 @@ # limitations under the License. """Utility functions related to the construction of the taylor form Hamiltonian.""" import itertools -import subprocess +from pathlib import Path import h5py import numpy as np @@ -516,7 +516,7 @@ def _load_cform_onemode(num_proc, nmodes, quad_order): for rank in range(num_proc): f = h5py.File("cform_H1data" + f"_{rank}" + ".hdf5", "r+") local_ham_cform_onebody = f["H1"][()] - chunk = np.array_split(local_ham_cform_onebody, nmode_combos)[mode_combo] # + chunk = np.array_split(local_ham_cform_onebody, nmode_combos)[mode_combo] l1 += len(chunk) local_chunk[l0:l1] = chunk l0 += len(chunk) @@ -767,17 +767,16 @@ def christiansen_integrals(pes, n_states=16, cubic=False): local_ham_cform_onebody = _cform_onemode(pes, n_states) comm.Barrier() - f = h5py.File("cform_H1data" + f"_{rank}" + ".hdf5", "w") - f.create_dataset("H1", data=local_ham_cform_onebody) - f.close() + file_path = Path(f"cform_H1data_{rank}.hdf5") + with h5py.File(file_path, "w") as f: + f.create_dataset("H1", data=local_ham_cform_onebody) comm.Barrier() ham_cform_onebody = None if rank == 0: ham_cform_onebody = _load_cform_onemode(size, len(pes.freqs), n_states) - process = subprocess.Popen("rm " + "cform_H1data*", stdout=subprocess.PIPE, shell=True) - _, _ = process.communicate() - + for path in Path.cwd().glob("cform_H1data*"): + path.unlink() comm.Barrier() ham_cform_onebody = comm.bcast(ham_cform_onebody, root=0) @@ -786,34 +785,32 @@ def christiansen_integrals(pes, n_states=16, cubic=False): local_ham_cform_twobody += _cform_twomode_kinetic(pes, n_states) comm.Barrier() - f = h5py.File("cform_H2data" + f"_{rank}" + ".hdf5", "w") - f.create_dataset("H2", data=local_ham_cform_twobody) - f.close() + file_path = Path(f"cform_H2data_{rank}.hdf5") + with h5py.File(file_path, "w") as f: + f.create_dataset("H2", data=local_ham_cform_twobody) comm.Barrier() ham_cform_twobody = None if rank == 0: ham_cform_twobody = _load_cform_twomode(size, len(pes.freqs), n_states) - process = subprocess.Popen("rm " + "cform_H2data*", stdout=subprocess.PIPE, shell=True) - _, _ = process.communicate() - + for path in Path.cwd().glob("cform_H2data*"): + path.unlink() comm.Barrier() ham_cform_twobody = comm.bcast(ham_cform_twobody, root=0) if cubic: local_ham_cform_threebody = _cform_threemode(pes, n_states) - f = h5py.File("cform_H3data" + f"_{rank}" + ".hdf5", "w") - f.create_dataset("H3", data=local_ham_cform_threebody) - f.close() + file_path = Path(f"cform_H3data_{rank}.hdf5") + with h5py.File(file_path, "w") as f: + f.create_dataset("H3", data=local_ham_cform_threebody) comm.Barrier() ham_cform_threebody = None if rank == 0: ham_cform_threebody = _load_cform_threemode(size, len(pes.freqs), n_states) - process = subprocess.Popen("rm " + "cform_H3data*", stdout=subprocess.PIPE, shell=True) - _, _ = process.communicate() - + for path in Path.cwd().glob("cform_H3data*"): + path.unlink() comm.Barrier() ham_cform_threebody = comm.bcast(ham_cform_threebody, root=0) @@ -838,17 +835,16 @@ def christiansen_integrals_dipole(pes, n_states=16): local_dipole_cform_onebody = _cform_onemode_dipole(pes, n_states) comm.Barrier() - f = h5py.File("cform_D1data" + f"_{rank}" + ".hdf5", "w") - f.create_dataset("D1", data=local_dipole_cform_onebody) - f.close() + file_path = Path(f"cform_D1data_{rank}.hdf5") + with h5py.File(file_path, "w") as f: + f.create_dataset("D1", data=local_dipole_cform_onebody) comm.Barrier() dipole_cform_onebody = None if rank == 0: dipole_cform_onebody = _load_cform_onemode_dipole(size, len(pes.freqs), n_states) - process = subprocess.Popen("rm " + "cform_D1data*", stdout=subprocess.PIPE, shell=True) - _, _ = process.communicate() - + for path in Path.cwd().glob("cform_D1data*"): + path.unlink() comm.Barrier() dipole_cform_onebody = comm.bcast(dipole_cform_onebody, root=0) @@ -856,16 +852,16 @@ def christiansen_integrals_dipole(pes, n_states=16): local_dipole_cform_twobody = _cform_twomode_dipole(pes, n_states) comm.Barrier() - f = h5py.File("cform_D2data" + f"_{rank}" + ".hdf5", "w") - f.create_dataset("D2", data=local_dipole_cform_twobody) - f.close() + file_path = Path(f"cform_D2data_{rank}.hdf5") + with h5py.File(file_path, "w") as f: + f.create_dataset("D2", data=local_dipole_cform_twobody) comm.Barrier() dipole_cform_twobody = None if rank == 0: dipole_cform_twobody = _load_cform_twomode_dipole(size, len(pes.freqs), n_states) - process = subprocess.Popen("rm " + "cform_D2data*", stdout=subprocess.PIPE, shell=True) - _, _ = process.communicate() + for path in Path.cwd().glob("cform_D2data*"): + path.unlink() comm.Barrier() dipole_cform_twobody = comm.bcast(dipole_cform_twobody, root=0) @@ -873,17 +869,17 @@ def christiansen_integrals_dipole(pes, n_states=16): local_dipole_cform_threebody = _cform_threemode_dipole(pes, n_states) comm.Barrier() - f = h5py.File("cform_D3data" + f"_{rank}" + ".hdf5", "w") - f.create_dataset("D3", data=local_dipole_cform_threebody) - f.close() + file_path = Path(f"cform_D3data_{rank}.hdf5") + with h5py.File(file_path, "w") as f: + f.create_dataset("D3", data=local_dipole_cform_threebody) + comm.Barrier() dipole_cform_threebody = None if rank == 0: dipole_cform_threebody = _load_cform_threemode_dipole(size, len(pes.freqs), n_states) - process = subprocess.Popen("rm " + "cform_D3data*", stdout=subprocess.PIPE, shell=True) - _, _ = process.communicate() + for path in Path.cwd().glob("cform_D3data*"): + path.unlink() comm.Barrier() - dipole_cform_threebody = comm.bcast(dipole_cform_threebody, root=0) D_arr = [dipole_cform_onebody, dipole_cform_twobody, dipole_cform_threebody] diff --git a/pennylane/ops/functions/equal.py b/pennylane/ops/functions/equal.py index c7afc235b10..f40769a7dc3 100644 --- a/pennylane/ops/functions/equal.py +++ b/pennylane/ops/functions/equal.py @@ -754,39 +754,6 @@ def _equal_counts(op1: CountsMP, op2: CountsMP, **kwargs): return _equal_measurements(op1, op2, **kwargs) and op1.all_outcomes == op2.all_outcomes -@_equal_dispatch.register -# pylint: disable=unused-argument -def _equal_basis_rotation( - op1: qml.BasisRotation, - op2: qml.BasisRotation, - check_interface=True, - check_trainability=True, - rtol=1e-5, - atol=1e-9, -): - if not qml.math.allclose( - op1.hyperparameters["unitary_matrix"], - op2.hyperparameters["unitary_matrix"], - atol=atol, - rtol=rtol, - ): - return ( - "The hyperparameter unitary_matrix is not equal for op1 and op2.\n" - f"Got {op1.hyperparameters['unitary_matrix']}\n and {op2.hyperparameters['unitary_matrix']}." - ) - if op1.wires != op2.wires: - return f"op1 and op2 have different wires. Got {op1.wires} and {op2.wires}." - if check_interface: - interface1 = qml.math.get_interface(op1.hyperparameters["unitary_matrix"]) - interface2 = qml.math.get_interface(op2.hyperparameters["unitary_matrix"]) - if interface1 != interface2: - return ( - "The hyperparameter unitary_matrix has different interfaces for op1 and op2." - f" Got {interface1} and {interface2}." - ) - return True - - @_equal_dispatch.register def _equal_hilbert_schmidt( op1: qml.HilbertSchmidt, diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index e00c479b4e4..f0c616554b2 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -56,7 +56,7 @@ def from_openfermion(openfermion_op, wires=None, tol=1e-16): tol (float): Tolerance for discarding negligible coefficients. Returns: - Union[FermiWord, FermiSentence, LinearCombination]: PennyLane operator. + Union[~.FermiWord, ~.FermiSentence, LinearCombination]: PennyLane operator. **Example** @@ -113,7 +113,7 @@ def to_openfermion( `FermionOperator `__. Args: - pennylane_op (~ops.op_math.Sum, ~ops.op_math.LinearCombination, FermiWord, FermiSentence): + pennylane_op (~ops.op_math.Sum, ~ops.op_math.LinearCombination, ~.FermiWord, ~.FermiSentence): PennyLane operator wires (dict): Custom wire mapping used to convert a PennyLane qubit operator to the external operator. @@ -126,9 +126,9 @@ def to_openfermion( **Example** >>> import pennylane as qml - >>> w1 = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'}) - >>> w2 = qml.fermi.FermiWord({(0, 1) : '+', (1, 2) : '-'}) - >>> fermi_s = qml.fermi.FermiSentence({w1 : 1.2, w2: 3.1}) + >>> w1 = qml.FermiWord({(0, 0) : '+', (1, 1) : '-'}) + >>> w2 = qml.FermiWord({(0, 1) : '+', (1, 2) : '-'}) + >>> fermi_s = qml.FermiSentence({w1 : 1.2, w2: 3.1}) >>> of_fermi_op = qml.to_openfermion(fermi_s) >>> of_fermi_op 1.2 [0^ 1] + diff --git a/pennylane/qchem/observable_hf.py b/pennylane/qchem/observable_hf.py index 4ef1fe1f9a3..5002168ff3d 100644 --- a/pennylane/qchem/observable_hf.py +++ b/pennylane/qchem/observable_hf.py @@ -32,7 +32,7 @@ def fermionic_observable(constant, one=None, two=None, cutoff=1.0e-12): cutoff (float): cutoff value for discarding the negligible integrals Returns: - FermiSentence: fermionic observable + ~.FermiSentence: fermionic observable **Example** @@ -98,7 +98,7 @@ def qubit_observable(o_ferm, cutoff=1.0e-12, mapping="jordan_wigner"): r"""Convert a fermionic observable to a PennyLane qubit observable. Args: - o_ferm (Union[FermiWord, FermiSentence]): fermionic operator + o_ferm (Union[~.FermiWord, ~.FermiSentence]): fermionic operator cutoff (float): cutoff value for discarding the negligible terms mapping (str): Specifies the fermion-to-qubit mapping. Input values can be ``'jordan_wigner'``, ``'parity'`` or ``'bravyi_kitaev'``. @@ -107,9 +107,9 @@ def qubit_observable(o_ferm, cutoff=1.0e-12, mapping="jordan_wigner"): **Example** - >>> w1 = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'}) - >>> w2 = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'}) - >>> s = qml.fermi.FermiSentence({w1 : 1.2, w2: 3.1}) + >>> w1 = qml.FermiWord({(0, 0) : '+', (1, 1) : '-'}) + >>> w2 = qml.FermiWord({(0, 0) : '+', (1, 1) : '-'}) + >>> s = qml.FermiSentence({w1 : 1.2, w2: 3.1}) >>> print(qubit_observable(s)) -0.775j * (Y(0) @ X(1)) + 0.775 * (Y(0) @ Y(1)) + 0.775 * (X(0) @ X(1)) + 0.775j * (X(0) @ Y(1)) """ diff --git a/pennylane/qchem/structure.py b/pennylane/qchem/structure.py index b8504af8d9c..02d84c9591b 100644 --- a/pennylane/qchem/structure.py +++ b/pennylane/qchem/structure.py @@ -292,9 +292,9 @@ def excitations(electrons, orbitals, delta_sz=0, fermionic=False): if not fermionic: return singles, doubles - fermionic_singles = [qml.fermi.FermiWord({(0, x[0]): "+", (1, x[1]): "-"}) for x in singles] + fermionic_singles = [qml.FermiWord({(0, x[0]): "+", (1, x[1]): "-"}) for x in singles] fermionic_doubles = [ - qml.fermi.FermiWord({(0, x[0]): "+", (1, x[1]): "+", (2, x[2]): "-", (3, x[3]): "-"}) + qml.FermiWord({(0, x[0]): "+", (1, x[1]): "+", (2, x[2]): "-", (3, x[3]): "-"}) for x in doubles ] diff --git a/pennylane/templates/subroutines/basis_rotation.py b/pennylane/templates/subroutines/basis_rotation.py index 832a0684739..f522dcb8b09 100644 --- a/pennylane/templates/subroutines/basis_rotation.py +++ b/pennylane/templates/subroutines/basis_rotation.py @@ -107,6 +107,10 @@ def _primitive_bind_call(cls, wires, unitary_matrix, check=False, id=None): return cls._primitive.bind(*wires, unitary_matrix, check=check, id=id) + @classmethod + def _unflatten(cls, data, metadata): + return cls(wires=metadata[0], unitary_matrix=data[0]) + def __init__(self, wires, unitary_matrix, check=False, id=None): M, N = qml.math.shape(unitary_matrix) @@ -124,19 +128,15 @@ def __init__(self, wires, unitary_matrix, check=False, id=None): if len(wires) < 2: raise ValueError(f"This template requires at least two wires, got {len(wires)}") - self._hyperparameters = { - "unitary_matrix": unitary_matrix, - } - - super().__init__(wires=wires, id=id) + super().__init__(unitary_matrix, wires=wires, id=id) @property def num_params(self): - return 0 + return 1 @staticmethod def compute_decomposition( - wires, unitary_matrix, check=False + unitary_matrix, wires, check=False ): # pylint: disable=arguments-differ r"""Representation of the operator as a product of other operators. diff --git a/tests/bose/test_binary_mapping.py b/tests/bose/test_binary_mapping.py index 4f8dff4d591..d9cdb384c38 100644 --- a/tests/bose/test_binary_mapping.py +++ b/tests/bose/test_binary_mapping.py @@ -548,7 +548,7 @@ def test_binary_mapping_wiremap(bose_op, wire_map, result): def test_error_is_raised_for_incompatible_type(): """Test that an error is raised if the input is not a BoseWord or BoseSentence""" - with pytest.raises(ValueError, match="bose_operator must be a BoseWord or BoseSentence"): + with pytest.raises(TypeError, match="bose_operator must be a BoseWord or BoseSentence"): binary_mapping(X(0)) diff --git a/tests/bose/test_christiansen_mapping.py b/tests/bose/test_christiansen_mapping.py index 121d679bfe7..085c45d3ac4 100644 --- a/tests/bose/test_christiansen_mapping.py +++ b/tests/bose/test_christiansen_mapping.py @@ -388,5 +388,5 @@ def test_christiansen_mapping_tolerance(bose_op, qubit_op_data, tol): def test_error_is_raised_for_incompatible_type(): """Test that an error is raised if the input is not a BoseWord or BoseSentence""" - with pytest.raises(ValueError, match="bose_operator must be a BoseWord or BoseSentence"): + with pytest.raises(TypeError, match="bose_operator must be a BoseWord or BoseSentence"): christiansen_mapping(X(0)) diff --git a/tests/bose/test_unary_mapping.py b/tests/bose/test_unary_mapping.py index edebef362ad..51c064fd0ee 100644 --- a/tests/bose/test_unary_mapping.py +++ b/tests/bose/test_unary_mapping.py @@ -517,7 +517,7 @@ def test_n_states_error_unary(): def test_error_is_raised_for_incompatible_type(): """Test that an error is raised if the input is not a BoseWord or BoseSentence""" - with pytest.raises(ValueError, match="bose_operator must be a BoseWord or BoseSentence"): + with pytest.raises(TypeError, match="bose_operator must be a BoseWord or BoseSentence"): unary_mapping(X(0)) diff --git a/tests/devices/test_legacy_device.py b/tests/devices/test_legacy_device.py index 671425692d6..bd8d2e7b2df 100644 --- a/tests/devices/test_legacy_device.py +++ b/tests/devices/test_legacy_device.py @@ -278,6 +278,20 @@ def test_supports_observable_exception(self, mock_device): ): dev.supports_observable(operation) + @pytest.mark.parametrize("supported_multi_term_obs", ["Hamiltonian", "LinearCombination"]) + @pytest.mark.parametrize("obs_type", [qml.ops.LinearCombination, qml.Hamiltonian]) + def test_all_multi_term_obs_supported_linear_combination( + self, mock_device, supported_multi_term_obs, obs_type + ): + """Test that LinearCombination is supported when the device supports either + LinearCombination or Hamiltonian.""" + dev = mock_device() + dev.observables = dev.observables + [supported_multi_term_obs] + + obs = obs_type([1.0, 2.0], [qml.Z(0), qml.Z(0)]) + circuit = qml.tape.QuantumScript([], [qml.expval(obs)]) + assert dev._all_multi_term_obs_supported(circuit) # pylint: disable=protected-access + class TestInternalFunctions: # pylint:disable=too-many-public-methods """Test the internal functions of the abstract Device class""" diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py index 2a6f5270616..3ef0e9be2da 100644 --- a/tests/ops/functions/test_equal.py +++ b/tests/ops/functions/test_equal.py @@ -2598,7 +2598,7 @@ def test_different_tolerances_comparison(self, op, other_op): assert_equal(op, other_op, atol=1e-5) assert qml.equal(op, other_op, rtol=0, atol=1e-9) is False - with pytest.raises(AssertionError, match="The hyperparameter unitary_matrix is not equal"): + with pytest.raises(AssertionError, match="op1 and op2 have different data"): assert_equal(op, other_op, rtol=0, atol=1e-9) @pytest.mark.parametrize("op, other_op", [(op1, op2)]) @@ -2629,7 +2629,7 @@ def test_non_equal_interfaces(self, op): assert_equal(op, other_op, check_interface=False) assert qml.equal(op, other_op) is False - with pytest.raises(AssertionError, match=r"has different interfaces for op1 and op2"): + with pytest.raises(AssertionError, match=r"have different interfaces"): assert_equal(op, other_op) diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py index cb2cc19020f..fa3e1cf7bc0 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py +++ b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py @@ -183,7 +183,7 @@ def test_convert_fermionic_type_fw(self): of_op = openfermion.FermionOperator("2^ 3") converted_op = qml.qchem.from_openfermion(of_op) - assert isinstance(converted_op, qml.fermi.FermiWord) + assert isinstance(converted_op, qml.FermiWord) def test_convert_fermionic_type_fs(self): r"""Test that FermiSentence object is returned when there are multiple @@ -192,7 +192,7 @@ def test_convert_fermionic_type_fs(self): of_op = openfermion.FermionOperator("2^ 3") + openfermion.FermionOperator("1^ 2") converted_op = qml.qchem.from_openfermion(of_op) - assert isinstance(converted_op, qml.fermi.FermiSentence) + assert isinstance(converted_op, qml.FermiSentence) def test_tol_fermionic(self): r"""Test that terms with coefficients larger than tolerance are discarded""" @@ -226,19 +226,17 @@ def test_fail_import_openfermion_fermionic(self, monkeypatch): class TestToOpenFermion: FERMI_AND_OF_OPS = ( - ((qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"})), (openfermion.FermionOperator("0^ 1"))), + ((qml.FermiWord({(0, 0): "+", (1, 1): "-"})), (openfermion.FermionOperator("0^ 1"))), ( - (qml.fermi.FermiWord({(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"})), + (qml.FermiWord({(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"})), (openfermion.FermionOperator("1 0^ 3^ 2")), ), ( ( - qml.fermi.FermiSentence( + qml.FermiSentence( { - qml.fermi.FermiWord( - {(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"} - ): 0.5, - qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"}): 0.3, + qml.FermiWord({(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"}): 0.5, + qml.FermiWord({(0, 0): "+", (1, 1): "-"}): 0.3, } ) ), @@ -272,12 +270,10 @@ def test_conversion(self, fermi_op, of_op): COMPLEX_OPS = ( ( - qml.fermi.FermiSentence( + qml.FermiSentence( { - qml.fermi.FermiWord( - {(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"} - ): 1e-08j, - qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"}): 0.3, + qml.FermiWord({(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"}): 1e-08j, + qml.FermiWord({(0, 0): "+", (1, 1): "-"}): 0.3, } ) ), @@ -339,9 +335,9 @@ def test_invalid_op(self): ) OPS_FERMI_WIRE = ( - ((qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"})), ({0: "a", 1: 2})), + ((qml.FermiWord({(0, 0): "+", (1, 1): "-"})), ({0: "a", 1: 2})), ( - (qml.fermi.FermiSentence({qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"}): 1.2})), + (qml.FermiSentence({qml.FermiWord({(0, 0): "+", (1, 1): "-"}): 1.2})), ({0: "a", 1: 2}), ), ) diff --git a/tests/templates/test_subroutines/test_basis_rotation.py b/tests/templates/test_subroutines/test_basis_rotation.py index a4328ca5264..0f9f6412a2b 100644 --- a/tests/templates/test_subroutines/test_basis_rotation.py +++ b/tests/templates/test_subroutines/test_basis_rotation.py @@ -21,7 +21,6 @@ import pennylane as qml -@pytest.mark.xfail # to be fixed by [sc-51603] def test_standard_validity(): """Run standard tests of operation validity.""" weights = np.array( @@ -402,8 +401,9 @@ def test_autograd(self, tol): assert np.allclose(grads, np.zeros_like(unitary_matrix, dtype=complex), atol=tol, rtol=0) + @pytest.mark.parametrize("device_name", ("default.qubit", "reference.qubit")) @pytest.mark.jax - def test_jax_jit(self, tol): + def test_jax_jit(self, device_name, tol): """Test the jax interface.""" import jax @@ -417,7 +417,7 @@ def test_jax_jit(self, tol): ] ) - dev = qml.device("default.qubit", wires=3) + dev = qml.device(device_name, wires=3) circuit = jax.jit(qml.QNode(circuit_template, dev), static_argnames="check") circuit2 = qml.QNode(circuit_template, dev)