Skip to content

Commit

Permalink
Merge branch 'master' into clifford-t-decomp
Browse files Browse the repository at this point in the history
  • Loading branch information
timmysilv committed Oct 16, 2023
2 parents dff58fb + e68a56f commit c755168
Show file tree
Hide file tree
Showing 118 changed files with 4,128 additions and 2,440 deletions.
31 changes: 30 additions & 1 deletion doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@
to the new transform program system.
[(#4573)](https://github.com/PennyLaneAI/pennylane/pull/4573)

* Transforms can be applied on devices following the new device API.
[(#4667)](https://github.com/PennyLaneAI/pennylane/pull/4667)

* All gradient transforms are updated to the new transform program system.
[(#4595)](https://github.com/PennyLaneAI/pennylane/pull/4595)

* All quantum functions transforms are update to the new transform program system.
[(#4439)](https://github.com/PennyLaneAI/pennylane/pull/4439)

Expand All @@ -80,8 +86,10 @@

* `default.qubit` now tracks the number of equivalent qpu executions and total shots
when the device is sampling. Note that `"simulations"` denotes the number of simulation passes, where as
`"executions"` denotes how many different computational bases need to be sampled in.
`"executions"` denotes how many different computational bases need to be sampled in. Additionally, the
new `default.qubit` also tracks the results of `device.execute`.
[(#4628)](https://github.com/PennyLaneAI/pennylane/pull/4628)
[(#4649)](https://github.com/PennyLaneAI/pennylane/pull/4649)

* The `JacobianProductCalculator` abstract base class and implementation `TransformJacobianProducts`
have been added to `pennylane.interfaces.jacobian_products`.
Expand Down Expand Up @@ -181,6 +189,24 @@
interface-specific scalar data, eg `[(tf.Variable(1.1), tf.Variable(2.2))]`.
[(#4603)](https://github.com/PennyLaneAI/pennylane/pull/4603)

* When decomposing a unitary matrix with `one_qubit_decomposition`, and opting to include the `GlobalPhase`
in the decomposition, the phase is no longer cast to `dtype=complex`.
[(#4653)](https://github.com/PennyLaneAI/pennylane/pull/4653)

* `qml.cut_circuit` is now compatible with circuits that compute the expectation values of Hamiltonians
with two or more terms.
[(#4642)](https://github.com/PennyLaneAI/pennylane/pull/4642)


* `_qfunc_output` has been removed from `QuantumScript`, as it is no longer necessary. There is
still a `_qfunc_output` property on `QNode` instances.
[(#4651)](https://github.com/PennyLaneAI/pennylane/pull/4651)

* The `qml.jordan_wigner` function has been modified to optionally remove the imaginary components
of the computed qubit operator, if imaginary components are smaller than a threshold.
[(#4639)](https://github.com/PennyLaneAI/pennylane/pull/4639)


<h3>Breaking changes 💔</h3>

* The device test suite now converts device kwargs to integers or floats if they can be converted to integers or floats.
Expand Down Expand Up @@ -326,6 +352,9 @@

<h3>Bug fixes 🐛</h3>

* Providing `work_wires=None` to `qml.GroverOperator` no longer interprets `None` as a wire.
[(#4668)](https://github.com/PennyLaneAI/pennylane/pull/4668)

* Fixed issue where `__copy__` method of the `qml.Select()` operator attempted to access un-initialized data.
[(#4551)](https://github.com/PennyLaneAI/pennylane/pull/4551)

Expand Down
1 change: 0 additions & 1 deletion pennylane/_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ def _local_tape_expand(tape, depth, stop_at):
new_tape.all_sampled = tape.all_sampled
new_tape._batch_size = tape.batch_size
new_tape._output_dim = tape.output_dim
new_tape._qfunc_output = tape._qfunc_output
return new_tape


Expand Down
1 change: 0 additions & 1 deletion pennylane/_grad.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,6 @@ def _jacobian_function(*args, **kwargs):
"If this is unintended, please add trainable parameters via the "
"'requires_grad' attribute or 'argnum' keyword."
)

jac = tuple(_jacobian(func, arg)(*args, **kwargs) for arg in _argnum)

return jac[0] if unpack else jac
Expand Down
42 changes: 24 additions & 18 deletions pennylane/devices/default_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def f(x):
* ``resources``: the :class:`~.resource.Resources` for the executed circuit.
* ``simulations``: the number of simulations performed. One simulation can cover multiple QPU executions, such as for non-commuting measurements and batched parameters.
* ``batches``: The number of times :meth:`~.execute` is called.
* ``results``: The results of each call of :meth:`~.execute`
* ``derivative_batches``: How many times :meth:`~.compute_derivatives` is called.
* ``execute_and_derivative_batches``: How many times :meth:`~.execute_and_compute_derivatives` is called
* ``vjp_batches``: How many times :meth:`~.compute_vjp` is called
Expand Down Expand Up @@ -298,24 +299,6 @@ def execute(
is_single_circuit = True
circuits = [circuits]

if self.tracker.active:
self.tracker.update(batches=1)
self.tracker.record()
for c in circuits:
qpu_executions, shots = get_num_shots_and_executions(c)
if c.shots:
self.tracker.update(
simulations=1,
executions=qpu_executions,
shots=shots,
resources=c.specs["resources"],
)
else:
self.tracker.update(
simulations=1, executions=qpu_executions, resources=c.specs["resources"]
)
self.tracker.record()

max_workers = execution_config.device_options.get("max_workers", self._max_workers)
interface = (
execution_config.interface
Expand Down Expand Up @@ -349,6 +332,29 @@ def execute(
# reset _rng to mimic serial behavior
self._rng = np.random.default_rng(self._rng.integers(2**31 - 1))

if self.tracker.active:
self.tracker.update(batches=1)
self.tracker.record()
for i, c in enumerate(circuits):
qpu_executions, shots = get_num_shots_and_executions(c)
res = np.array(results[i]) if isinstance(results[i], Number) else results[i]
if c.shots:
self.tracker.update(
simulations=1,
executions=qpu_executions,
results=res,
shots=shots,
resources=c.specs["resources"],
)
else:
self.tracker.update(
simulations=1,
executions=qpu_executions,
results=res,
resources=c.specs["resources"],
)
self.tracker.record()

return results[0] if is_single_circuit else results

def compute_derivatives(
Expand Down
6 changes: 4 additions & 2 deletions pennylane/devices/qubit/sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,8 @@ def sample_state(
basis_states = np.arange(2**num_wires)

flat_state = flatten_state(state, total_indices)
probs = qml.probs(wires=wires_to_sample).process_state(flat_state, state_wires)
with qml.queuing.QueuingManager.stop_recording():
probs = qml.probs(wires=wires_to_sample).process_state(flat_state, state_wires)

if is_state_batched:
# rng.choice doesn't support broadcasting
Expand Down Expand Up @@ -473,7 +474,8 @@ def _sample_state_jax(
basis_states = np.arange(2**num_wires)

flat_state = flatten_state(state, total_indices)
probs = qml.probs(wires=wires_to_sample).process_state(flat_state, state_wires)
with qml.queuing.QueuingManager.stop_recording():
probs = qml.probs(wires=wires_to_sample).process_state(flat_state, state_wires)

if is_state_batched:
# Produce separate keys for each of the probabilities along the broadcasted axis
Expand Down
5 changes: 4 additions & 1 deletion pennylane/drawer/draw.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,13 @@ def wrapper(*args, **kwargs):
_wire_order = wire_order or qnode.tape.wires
else:
original_expansion_strategy = getattr(qnode, "expansion_strategy", None)

try:
qnode.expansion_strategy = expansion_strategy or original_expansion_strategy
tapes = qnode.construct(args, kwargs)
if isinstance(qnode.device, qml.devices.Device):
program = qnode.transform_program
tapes = program([qnode.tape])

finally:
qnode.expansion_strategy = original_expansion_strategy

Expand Down
28 changes: 21 additions & 7 deletions pennylane/fermi/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@

from functools import singledispatch
from typing import Union

import pennylane as qml
from pennylane.operation import Operator
from pennylane.pauli import PauliWord, PauliSentence
from .fermionic import FermiWord, FermiSentence
from pennylane.pauli import PauliSentence, PauliWord

from .fermionic import FermiSentence, FermiWord


# pylint: disable=unexpected-keyword-arg
def jordan_wigner(
fermi_operator: (Union[FermiWord, FermiSentence]), ps: bool = False, wire_map: dict = None
fermi_operator: (Union[FermiWord, FermiSentence]),
ps: bool = False,
wire_map: dict = None,
tol: float = None,
) -> Union[Operator, PauliSentence]:
r"""Convert a fermionic operator to a qubit operator using the Jordan-Wigner mapping.
Expand All @@ -49,6 +55,7 @@ def jordan_wigner(
wire_map (dict): a dictionary defining how to map the oribitals of
the Fermi operator to qubit wires. If None, the integers used to
order the orbitals will be used as wire labels. Defaults to None.
tol (float): tolerance for discarding the imaginary part of the coefficients
Returns:
Union[PauliSentence, Operator]: a linear combination of qubit operators
Expand All @@ -72,17 +79,17 @@ def jordan_wigner(
+ (0.25+0j) * X(2) @ X(3)
+ 0.25j * X(2) @ Y(3)
"""
return _jordan_wigner_dispatch(fermi_operator, ps, wire_map)
return _jordan_wigner_dispatch(fermi_operator, ps, wire_map, tol)


@singledispatch
def _jordan_wigner_dispatch(fermi_operator, ps, wire_map):
def _jordan_wigner_dispatch(fermi_operator, ps, wire_map, tol):
"""Dispatches to appropriate function if fermi_operator is a FermiWord or FermiSentence."""
raise ValueError(f"fermi_operator must be a FermiWord or FermiSentence, got: {fermi_operator}")


@_jordan_wigner_dispatch.register
def _(fermi_operator: FermiWord, ps=False, wire_map=None):
def _(fermi_operator: FermiWord, ps=False, wire_map=None, tol=None):
wires = list(fermi_operator.wires) or [0]
identity_wire = wires[0]

Expand All @@ -104,6 +111,10 @@ def _(fermi_operator: FermiWord, ps=False, wire_map=None):
}
)

for pw in qubit_operator:
if tol is not None and abs(qml.math.imag(qubit_operator[pw])) <= tol:
qubit_operator[pw] = qml.math.real(qubit_operator[pw])

if not ps:
# wire_order specifies wires to use for Identity (PauliWord({}))
qubit_operator = qubit_operator.operation(wire_order=[identity_wire])
Expand All @@ -115,7 +126,7 @@ def _(fermi_operator: FermiWord, ps=False, wire_map=None):


@_jordan_wigner_dispatch.register
def _(fermi_operator: FermiSentence, ps=False, wire_map=None):
def _(fermi_operator: FermiSentence, ps=False, wire_map=None, tol=None):
wires = list(fermi_operator.wires) or [0]
identity_wire = wires[0]

Expand All @@ -127,6 +138,9 @@ def _(fermi_operator: FermiSentence, ps=False, wire_map=None):
for pw in fermi_word_as_ps:
qubit_operator[pw] = qubit_operator[pw] + fermi_word_as_ps[pw] * coeff

if tol is not None and abs(qml.math.imag(qubit_operator[pw])) <= tol:
qubit_operator[pw] = qml.math.real(qubit_operator[pw])

if not ps:
qubit_operator = qubit_operator.operation(wire_order=[identity_wire])

Expand Down
43 changes: 38 additions & 5 deletions pennylane/gradients/finite_difference.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,28 @@
This module contains functions for computing the finite-difference gradient
of a quantum tape.
"""
# pylint: disable=protected-access,too-many-arguments,too-many-branches,too-many-statements
# pylint: disable=protected-access,too-many-arguments,too-many-branches,too-many-statements,unused-argument
from typing import Sequence, Callable
import functools
from functools import partial
from warnings import warn

import numpy as np
from scipy.special import factorial

import pennylane as qml
from pennylane.measurements import ProbabilityMP
from pennylane.transforms.core import transform
from pennylane.transforms.tape_expand import expand_invalid_trainable
from pennylane.gradients.gradient_transform import _contract_qjac_with_cjac


from .general_shift_rules import generate_shifted_tapes
from .gradient_transform import (
_all_zero_grad,
assert_no_tape_batching,
choose_grad_methods,
gradient_analysis_and_validation,
gradient_transform,
_no_trainable_grad,
)

Expand Down Expand Up @@ -167,17 +172,44 @@ def _processing_fn(results, shots, single_shot_batch_fn):
return tuple(grads_tuple)


@gradient_transform
def _expand_transform_finite_diff(
tape: qml.tape.QuantumTape,
argnum=None,
h=1e-7,
approx_order=1,
n=1,
strategy="forward",
f0=None,
validate_params=True,
) -> (Sequence[qml.tape.QuantumTape], Callable):
"""Expand function to be applied before finite difference."""
expanded_tape = expand_invalid_trainable(tape)

def null_postprocessing(results):
"""A postprocesing function returned by a transform that only converts the batch of results
into a result for a single ``QuantumTape``.
"""
return results[0]

return [expanded_tape], null_postprocessing


@partial(
transform,
expand_transform=_expand_transform_finite_diff,
classical_cotransform=_contract_qjac_with_cjac,
final_transform=True,
)
def finite_diff(
tape,
tape: qml.tape.QuantumTape,
argnum=None,
h=1e-7,
approx_order=1,
n=1,
strategy="forward",
f0=None,
validate_params=True,
):
) -> (Sequence[qml.tape.QuantumTape], Callable):
r"""Transform a QNode to compute the finite-difference gradient of all gate parameters with respect to its inputs.
Args:
Expand Down Expand Up @@ -318,6 +350,7 @@ def finite_diff(
The outermost tuple contains results corresponding to each element of the shot vector.
"""

transform_name = "finite difference"
assert_no_tape_batching(tape, transform_name)

Expand Down
14 changes: 9 additions & 5 deletions pennylane/gradients/gradient_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,13 +391,19 @@ def reorder_grads(grads, tape_specs):


# pylint: disable=too-many-return-statements,too-many-branches
def _contract_qjac_with_cjac(qjac, cjac, num_measurements, has_partitioned_shots):
def _contract_qjac_with_cjac(qjac, cjac, tape):
"""Contract a quantum Jacobian with a classical preprocessing Jacobian.
Essentially, this function computes the generalized version of
``tensordot(qjac, cjac)`` over the tape parameter axis, adapted to the new
return type system. This function takes the measurement shapes and different
QNode arguments into account.
"""
num_measurements = len(tape.measurements)
has_partitioned_shots = tape.shots.has_partitioned_shots

if isinstance(qjac, tuple) and len(qjac) == 1:
qjac = qjac[0]

if isinstance(cjac, tuple) and len(cjac) == 1:
cjac = cjac[0]

Expand Down Expand Up @@ -453,7 +459,7 @@ def _reshape(x):
return tuple(tuple(tdot(qml.math.stack(q), c) for c in cjac if c is not None) for q in qjac)


class gradient_transform(qml.batch_transform):
class gradient_transform(qml.batch_transform): # pragma: no cover
"""Decorator for defining quantum gradient transforms.
Quantum gradient transforms are a specific case of :class:`~.batch_transform`.
Expand Down Expand Up @@ -601,8 +607,6 @@ def jacobian_wrapper(
qnode, argnum=argnum_cjac, expand_fn=self.expand_fn
)(*args, **kwargs)

num_measurements = len(qnode.tape.measurements)
has_partitioned_shots = qnode.tape.shots.has_partitioned_shots
return _contract_qjac_with_cjac(qjac, cjac, num_measurements, has_partitioned_shots)
return _contract_qjac_with_cjac(qjac, cjac, qnode.tape) # pragma: no cover

return jacobian_wrapper
Loading

0 comments on commit c755168

Please sign in to comment.