Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restructures preprocess to include building block, extensible transforms #4659

Merged
merged 25 commits into from
Oct 16, 2023
Merged
Changes from 2 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0a17255
refactor preprocess transforms
albi3ro Oct 6, 2023
9ba52eb
Apply suggestions from code review
albi3ro Oct 6, 2023
57ce47b
cleaning and tests
albi3ro Oct 10, 2023
18ffd25
lots of moving tests around and fixing them
albi3ro Oct 10, 2023
bf4c569
try and fix conflict
albi3ro Oct 10, 2023
9026cf6
fix commit problem
albi3ro Oct 10, 2023
c07a926
Merge branch 'master' into cleaning-preproces
albi3ro Oct 10, 2023
bfa0c2c
fix defer measurements
albi3ro Oct 10, 2023
5feaf6e
fixing tests
albi3ro Oct 11, 2023
562739e
fix adjoint metric tensor
albi3ro Oct 11, 2023
7c6a891
move static method out of class
albi3ro Oct 11, 2023
aad6b74
fix tests and documentation
albi3ro Oct 11, 2023
fbd4662
more test fixes
albi3ro Oct 11, 2023
c861001
tests and changelog
albi3ro Oct 11, 2023
e85e94d
Merge branch 'master' into cleaning-preproces
albi3ro Oct 11, 2023
b18c48c
fix supports_derivatives
albi3ro Oct 11, 2023
29d6ee6
Update pennylane/devices/preprocess.py
albi3ro Oct 12, 2023
9ebe948
improved docstrings
albi3ro Oct 12, 2023
6780f81
Merge branch 'cleaning-preproces' of https://github.com/PennyLaneAI/p…
albi3ro Oct 12, 2023
f032726
Apply suggestions from code review
albi3ro Oct 13, 2023
6bb5cc6
Update tests/devices/default_qubit/test_default_qubit_preprocessing.py
albi3ro Oct 13, 2023
22c6756
Merge branch 'master' into cleaning-preproces
albi3ro Oct 13, 2023
93692cc
Merge branch 'master' into cleaning-preproces
vincentmr Oct 16, 2023
4182387
Test python 3.9
vincentmr Oct 16, 2023
75d9699
Revert CI constraints.
vincentmr Oct 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 83 additions & 11 deletions pennylane/devices/preprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def _operator_decomposition_gen(
acceptance_function: Callable[[qml.operation.Operator], bool],
name: str = "device",
) -> Generator[qml.operation.Operator, None, None]:
"""A generator that yields the next operation that is accepted by DefaultQubit."""
"""A generator that yields the next operation that is accepted."""
if acceptance_function(op):
yield op
else:
Expand All @@ -69,29 +69,43 @@ def _operator_decomposition_gen(
def no_sampling(
tape: qml.tape.QuantumTape, name="device"
albi3ro marked this conversation as resolved.
Show resolved Hide resolved
) -> (Sequence[qml.tape.QuantumTape], Callable):
"""Raises an error if the tape has finite shots."""
"""Raises an error if the tape has finite shots.

Args:
tape (QuantumTape): a quantum circuit
name="device" (str): name to use in error message.

This transform can be added to forbid finite shots. For example, ``default.qubit`` uses it for
adjoint and backprop valdiation.
albi3ro marked this conversation as resolved.
Show resolved Hide resolved
"""
if tape.shots:
raise qml.DeviceError(f"Finite shots are not supported with {name}")
return (tape,), null_postprocessing


@transform
def validate_device_wires(
tape: qml.tape.QuantumTape, wires: Optional[qml.wires.Wires] = None, name: str = ""
tape: qml.tape.QuantumTape, wires: Optional[qml.wires.Wires] = None, name: str = "device"
) -> (Sequence[qml.tape.QuantumTape], Callable):
"""Validates the device wires.
"""Validates that all wires present in the tape are in the set of provided wires. Adds the
device wires to measurement processes like :class:`~.measurements.StateMP` that are broadcasted
accross all available wires.
albi3ro marked this conversation as resolved.
Show resolved Hide resolved

Args:
tape (QuantumTape): a quantum circuit.
wires (Wires): the allowed wires
name (str): the name of the device to use in error messages.
wires=None (Optional[Wires]): the allowed wires. Wires of ``None`` allows any wires
to be present in the tape.
name="device" (str): the name of the device to use in error messages.

Returns:
pennylane.QNode or qfunc or Tuple[List[.QuantumTape], Callable]: If a QNode is passed,
it returns a QNode with the transform added to its transform program.
If a tape is passed, returns a tuple containing a list of
quantum tapes to be evaluated, and a function to be applied to these
tape executions.

Raises:
WireError: if the tape has a wire not present in the provided wires.
"""
if wires:
if extra_wires := set(tape.wires) - set(wires):
Expand Down Expand Up @@ -222,9 +236,45 @@ def decompose(
DecompositionUndefinedError: if an operator is not accepted and does not define a decomposition

DeviceError: If the decomposition enters and infinite loop and raises a ``RecursionError``.

**Example:**

>>> def stopping_condition(obj):
... return obj.name in {"CNOT", "RX", "RZ"}
>>> tape = qml.tape.QuantumScript([qml.IsingXX(1.2, wires=(0,1))], [qml.expval(qml.PauliZ(0))])
>>> batch, fn = decompose(tape, stopping_condition)
>>> batch[0].circuit
[CNOT(wires=[0, 1]),
RX(1.2, wires=[0]),
CNOT(wires=[0, 1]),
expval(PauliZ(wires=[0]))]

If an operator cannot be decomposed into a supported operation, an error is raised:

>>> decompose(tape, lambda obj: obj.name == "S")
DeviceError: Operator CNOT(wires=[0, 1]) not supported on device and does not provide a decomposition.

The ``skip_initial_state_prep`` specifies whether or not the device supports state prep operations
at the beginning of the circuit.

>>> tape = qml.tape.QuantumScript([qml.BasisState([1], wires=0), qml.BasisState([1], wires=1)])
>>> batch, fn = decompose(tape, stopping_condition)
>>> batch[0].circuit
[BasisState(array([1]), wires=[0]),
RZ(1.5707963267948966, wires=[1]),
RX(3.141592653589793, wires=[1]),
RZ(1.5707963267948966, wires=[1])]
>>> batch, fn = decompose(tape, stopping_condition, skip_initial_state_prep=False)
>>> batch[0].circuit
[RZ(1.5707963267948966, wires=[0]),
RX(3.141592653589793, wires=[0]),
RZ(1.5707963267948966, wires=[0]),
RZ(1.5707963267948966, wires=[1]),
RX(3.141592653589793, wires=[1]),
RZ(1.5707963267948966, wires=[1])]

"""

# TODO: check if this pre-check actually leads to any performance improvements
if not all(stopping_condition(op) for op in tape.operations):
try:
# don't decompose initial operations if its StatePrepBase
Expand Down Expand Up @@ -257,7 +307,7 @@ def validate_observables(

Args:
tape (QuantumTape): a quantum circuit.
observable_stopping_condition (callable): a function that specifies whether or not an observable is accepted.
stopping_condition (callable): a function that specifies whether or not an observable is accepted.
name (str): the name of the device to use in error messages.

Returns:
Expand All @@ -270,14 +320,25 @@ def validate_observables(
Raises:
DeviceError: if an observable is not supported

**Example:**

>>> def accepted_observable(obj):
... return obj.name in {"PauliX", "PauliY", "PauliZ"}
>>> tape = qml.tape.QuantumScript([], [qml.expval(qml.PauliZ(0) + qml.PauliY(0))])
>>> validate_observables(tape, accepted_observable)
DeviceError: Observable <Hamiltonian: terms=2, wires=[0]> not supported on device

Note that if the observable is a :class:`~.Tensor`, the validation is run on each object in the
``Tensor`` instead.

"""
for m in tape.measurements:
if m.obs is not None:
if isinstance(m.obs, Tensor):
if any(not stopping_condition(o) for o in m.obs.obs):
raise DeviceError(f"Observable {m.obs} not supported on {name}")
raise DeviceError(f"Observable {repr(m.obs)} not supported on {name}")
elif not stopping_condition(m.obs):
raise DeviceError(f"Observable {m.obs} not supported on {name}")
raise DeviceError(f"Observable {repr(m.obs)} not supported on {name}")

return (tape,), null_postprocessing

Expand All @@ -293,7 +354,7 @@ def validate_measurements(
analytic_measurements (Callable[[MeasurementProcess], bool]): a function from a measurement process
to whether or not it is accepted in analytic simulations.
sample_measurements (Callable[[MeasurementProcess], bool]): a function from a measurement process
to whetehr or not it accepted for finite shot siutations
to whether or not it accepted for finite shot siutations
name (str): the name to use in error messages.

Returns:
Expand All @@ -306,6 +367,17 @@ def validate_measurements(
Raises:
DeviceError: if a measurement process is not supported.

>>> def analytic_measurements(m):
... return isinstance(m, qml.measurements.StateMP)
>>> def shots_measurements(m):
... return isinstance(m, qml.measurements.CountsMP)
>>> tape = qml.tape.QuantumScript([], [qml.expval(qml.PauliZ(0))])
>>> validate_measurements(tape, analytic_measurements, shots_measurements)
DeviceError: Measurement expval(PauliZ(wires=[0])) not accepted for analytic simulation on device.
>>> tape = qml.tape.QuantumScript([], [qml.sample()], shots=10)
>>> validate_measurements(tape, analytic_measurements, shots_measurements)
DeviceError: Measurement sample(wires=[]) not accepted with finite shots on device

"""
if analytic_measurements is None:

Expand Down
Loading