Skip to content

Commit

Permalink
Merge branch 'no-grad-on-diff-method-none' of https://github.com/Penn…
Browse files Browse the repository at this point in the history
…yLaneAI/pennylane into no-grad-on-diff-method-none
  • Loading branch information
albi3ro committed Jan 8, 2025
2 parents 913c899 + 9ddbb58 commit 56d4c88
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 33 deletions.
3 changes: 3 additions & 0 deletions doc/releases/changelog-0.40.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,9 @@ same information.

<h3>Bug fixes 🐛</h3>

* Adds validation so the device vjp is only used when the device actually supports it.
[(#6755)](https://github.com/PennyLaneAI/pennylane/pull/6755/)

* `qml.counts` returns all outcomes when the `all_outcomes` argument is `True` and mid-circuit measurements are present.
[(#6732)](https://github.com/PennyLaneAI/pennylane/pull/6732)

Expand Down
4 changes: 4 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@

<h3>Documentation 📝</h3>

* Updated documentation for vibrational Hamiltonians
[(#6717)](https://github.com/PennyLaneAI/pennylane/pull/6717)

<h3>Bug fixes 🐛</h3>

<h3>Contributors ✍️</h3>

This release contains contributions from (in alphabetical order):
Diksha Dhawan
2 changes: 1 addition & 1 deletion pennylane/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
Version number (major.minor.patch[-label])
"""

__version__ = "0.41.0-dev0"
__version__ = "0.41.0-dev2"
55 changes: 35 additions & 20 deletions pennylane/bose/bosonic_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,16 @@ def binary_mapping(
>>> w = qml.bose.BoseWord({(0, 0): "+"})
>>> qml.binary_mapping(w, n_states=4)
0.6830127018922193 * X(0)
+ -0.1830127018922193 * X(0) @ Z(1)
+ -0.6830127018922193j * Y(0)
+ 0.1830127018922193j * Y(0) @ Z(1)
+ 0.3535533905932738 * X(0) @ X(1)
+ -0.3535533905932738j * X(0) @ Y(1)
+ 0.3535533905932738j * Y(0) @ X(1)
+ (0.3535533905932738+0j) * Y(0) @ Y(1)
(
0.6830127018922193 * X(0)
+ -0.1830127018922193 * X(0) @ Z(1)
+ -0.6830127018922193j * Y(0)
+ 0.1830127018922193j * Y(0) @ Z(1)
+ 0.3535533905932738 * X(0) @ X(1)
+ -0.3535533905932738j * X(0) @ Y(1)
+ 0.3535533905932738j * Y(0) @ X(1)
+ (0.3535533905932738+0j) * Y(0) @ Y(1)
)
"""

qubit_operator = _binary_mapping_dispatch(bose_operator, n_states, tol=tol)
Expand Down Expand Up @@ -190,18 +192,20 @@ def unary_mapping(
>>> w = qml.bose.BoseWord({(0, 0): "+"})
>>> qml.unary_mapping(w, n_states=4)
0.25 * X(0) @ X(1)
+ -0.25j * X(0) @ Y(1)
+ 0.25j * Y(0) @ X(1)
+ (0.25+0j) * Y(0) @ Y(1)
+ 0.3535533905932738 * X(1) @ X(2)
+ -0.3535533905932738j * X(1) @ Y(2)
+ 0.3535533905932738j * Y(1) @ X(2)
+ (0.3535533905932738+0j) * Y(1) @ Y(2)
+ 0.4330127018922193 * X(2) @ X(3)
+ -0.4330127018922193j * X(2) @ Y(3)
+ 0.4330127018922193j * Y(2) @ X(3)
+ (0.4330127018922193+0j) * Y(2) @ Y(3)
(
0.25 * X(0) @ X(1)
+ -0.25j * X(0) @ Y(1)
+ 0.25j * Y(0) @ X(1)
+ (0.25+0j) * Y(0) @ Y(1)
+ 0.3535533905932738 * X(1) @ X(2)
+ -0.3535533905932738j * X(1) @ Y(2)
+ 0.3535533905932738j * Y(1) @ X(2)
+ (0.3535533905932738+0j) * Y(1) @ Y(2)
+ 0.4330127018922193 * X(2) @ X(3)
+ -0.4330127018922193j * X(2) @ Y(3)
+ 0.4330127018922193j * Y(2) @ X(3)
+ (0.4330127018922193+0j) * Y(2) @ Y(3)
)
"""

qubit_operator = _unary_mapping_dispatch(bose_operator, n_states, tol=tol)
Expand Down Expand Up @@ -334,6 +338,17 @@ def christiansen_mapping(
Returns:
Union[PauliSentence, Operator]: A linear combination of qubit operators.
**Example**
>>> w = qml.bose.BoseWord({(0,0):"+", (1,1): "-"})
>>> qml.christiansen_mapping(w)
(
0.25 * (X(0) @ X(1))
+ 0.25j * (X(0) @ Y(1))
+ -0.25j * (Y(0) @ X(1))
+ (0.25+0j) * (Y(0) @ Y(1))
)
"""

qubit_operator = _christiansen_mapping_dispatch(bose_operator, tol)
Expand Down
16 changes: 16 additions & 0 deletions pennylane/qchem/vibrational/localize_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,22 @@ def localize_normal_modes(freqs, vecs, bins=[2600]):
- TensorLike[float] : localization matrix describing the relationship between
original and localized modes.
**Example**
>>> freqs = np.array([1326.66001461, 2297.26736859, 2299.65032901])
>>> vectors = np.array([[[ 5.71518696e-18, -4.55642350e-01, 5.20920552e-01],
[ 1.13167924e-17, 4.55642350e-01, 5.20920552e-01],
[-1.23163569e-17, 5.09494945e-12, -3.27565762e-02]],
[[-4.53008817e-17, 4.90364125e-01, 4.90363894e-01],
[-1.98591028e-16, 4.90361513e-01, -4.90361744e-01],
[-2.78235498e-18, -3.08350419e-02, -6.75886679e-08]],
[[ 5.75393451e-17, 5.37047963e-01, 4.41957355e-01],
[ 6.53049347e-17, -5.37050348e-01, 4.41959740e-01],
[-5.49709883e-17, 7.49851221e-08, -2.77912798e-02]]])
>>> freqs_loc, vecs_loc, uloc = qml.qchem.localize_normal_modes(freqs, vectors)
>>> freqs_loc
array([1332.62008773, 2296.73455892, 2296.7346082 ])
"""
if not bins:
raise ValueError("The `bins` list cannot be empty.")
Expand Down
33 changes: 33 additions & 0 deletions pennylane/qchem/vibrational/vibrational_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,28 @@ class VibrationalPES:
1, 2, or 3 for upto one-mode dipole, two-mode dipole and three-mode dipole, respectively. Default
value is 1.
**Example**
>>> freqs = np.array([0.01885397])
>>> grid, weights = np.polynomial.hermite.hermgauss(9)
>>> pes_onebody = [[0.05235573, 0.03093067, 0.01501878, 0.00420778, 0.0,
0.00584504, 0.02881817, 0.08483433, 0.22025702]]
>>> pes_twobody = None
>>> dipole_onebody = [[[-1.92201700e-16, 1.45397041e-16, -1.40451549e-01],
[-1.51005108e-16, 9.53185441e-17, -1.03377032e-01],
[-1.22793018e-16, 7.22781963e-17, -6.92825934e-02],
[-1.96537436e-16, -5.86686504e-19, -3.52245369e-02],
[ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],
[ 5.24758835e-17, -1.40650833e-16, 3.69955543e-02],
[-4.52407941e-17, 1.38406311e-16, 7.60888733e-02],
[-4.63820104e-16, 5.42928787e-17, 1.17726042e-01],
[ 1.19224372e-16, 9.12491386e-17, 1.64013197e-01]]]
>>> vib_obj = qml.qchem.VibrationalPES(freqs=freqs, grid=grid, gauss_weights=weights,
uloc = None, pes_data=[pes_onebody, pes_twobody],
dipole_data=[dipole_onebody], localized=False)
>>> vib_obj.freqs
array([0.01885397])
"""

def __init__(
Expand Down Expand Up @@ -150,6 +172,17 @@ def optimize_geometry(molecule, method="rhf"):
Returns:
array[array[float]]: optimized atomic positions in Cartesian coordinates
**Example**
>>> symbols = ['H', 'F']
>>> geometry = np.array([[0.0, 0.0, 0.0],
[0.0, 0.0, 1.0]])
>>> mol = qml.qchem.Molecule(symbols, geometry)
>>> eq_geom = qml.qchem.optimize_geometry(mol)
>>> eq_geom
array([[ 0. , 0. , -0.40277116],
[ 0. , 0. , 1.40277116]])
"""
pyscf = _import_pyscf()
geometric = _import_geometric()
Expand Down
24 changes: 12 additions & 12 deletions pennylane/workflow/resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,7 @@ def _use_tensorflow_autograph():
return not tf.executing_eagerly()


def _resolve_interface(
interface: Union[str, Interface, None], tapes: QuantumScriptBatch
) -> Interface:
def _resolve_interface(interface: Union[str, Interface], tapes: QuantumScriptBatch) -> Interface:
"""Helper function to resolve an interface based on a set of tapes.
Args:
Expand Down Expand Up @@ -249,22 +247,24 @@ def _resolve_execution_config(
):
updated_values["grad_on_execution"] = False

if execution_config.use_device_jacobian_product and isinstance(
device, qml.devices.LegacyDeviceFacade
):
raise qml.QuantumFunctionError(
"device provided jacobian products are not compatible with the old device interface."
)

if (
"lightning" in device.name
and (transform_program and qml.metric_tensor in transform_program)
and transform_program
and qml.metric_tensor in transform_program
and execution_config.gradient_method == "best"
):
execution_config = replace(execution_config, gradient_method=qml.gradients.param_shift)

execution_config = _resolve_diff_method(execution_config, device, tape=tapes[0])

if execution_config.use_device_jacobian_product and not device.supports_vjp(
execution_config, tapes[0]
):
raise qml.QuantumFunctionError(
f"device_vjp=True is not supported for device {device},"
f" diff_method {execution_config.gradient_method},"
" and the provided circuit."
)

if execution_config.gradient_method is qml.gradients.param_shift_cv:
updated_values["gradient_keyword_arguments"]["dev"] = device

Expand Down
22 changes: 22 additions & 0 deletions tests/test_qnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@ def circuit(x, y):

assert np.allclose(res, expected, atol=tol, rtol=0)

# pylint: disable=too-many-positional-arguments
@pytest.mark.parametrize("dev_name", ["default.qubit", "default.mixed"])
@pytest.mark.parametrize("first_par", np.linspace(0.15, np.pi - 0.3, 3))
@pytest.mark.parametrize("sec_par", np.linspace(0.15, np.pi - 0.3, 3))
Expand Down Expand Up @@ -1170,6 +1171,27 @@ def decomposition(self) -> list:
res = qml.execute([tape], dev)
assert qml.math.get_interface(res) == "numpy"

def test_error_device_vjp_unsuppoprted(self):
"""Test that an error is raised in the device_vjp is unsupported."""

class DummyDev(qml.devices.Device):

def execute(self, circuits, execution_config=qml.devices.ExecutionConfig()):
return 0

def supports_derivatives(self, execution_config=None, circuit=None):
return execution_config and execution_config.gradient_method == "vjp_grad"

def supports_vjp(self, execution_config=None, circuit=None) -> bool:
return execution_config and execution_config.gradient_method == "vjp_grad"

@qml.qnode(DummyDev(), diff_method="parameter-shift", device_vjp=True)
def circuit():
return qml.expval(qml.Z(0))

with pytest.raises(qml.QuantumFunctionError, match="device_vjp=True is not supported"):
circuit()


class TestShots:
"""Unit tests for specifying shots per call."""
Expand Down
27 changes: 27 additions & 0 deletions tests/workflow/test_resolve_execution_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,30 @@ def test_jax_jit_interface():
expected_mcm_config = MCMConfig(mcm_method="deferred", postselect_mode="fill-shots")

assert resolved_config.mcm_config == expected_mcm_config


# pylint: disable=unused-argument
def test_no_device_vjp_if_not_supported():
"""Test that an error is raised for device_vjp=True if the device does not support it."""

class DummyDev(qml.devices.Device):

def execute(self, circuits, execution_config=qml.devices.ExecutionConfig()):
return 0

def supports_derivatives(self, execution_config=None, circuit=None):
return execution_config and execution_config.gradient_method == "vjp_grad"

def supports_vjp(self, execution_config=None, circuit=None) -> bool:
return execution_config and execution_config.gradient_method == "vjp_grad"

config_vjp_grad = ExecutionConfig(use_device_jacobian_product=True, gradient_method="vjp_grad")
tape = qml.tape.QuantumScript()
# no error
_ = _resolve_execution_config(config_vjp_grad, DummyDev(), (tape,))

config_parameter_shift = ExecutionConfig(
use_device_jacobian_product=True, gradient_method="parameter-shift"
)
with pytest.raises(qml.QuantumFunctionError, match="device_vjp=True is not supported"):
_resolve_execution_config(config_parameter_shift, DummyDev(), (tape,))

0 comments on commit 56d4c88

Please sign in to comment.