Skip to content

Commit

Permalink
QA PRs checking process for PL 0.40 during feature freeze (#6775)
Browse files Browse the repository at this point in the history
As the title says.

---------

Co-authored-by: Andrija Paurevic <[email protected]>
  • Loading branch information
PietropaoloFrisoni and andrijapau authored Jan 9, 2025
1 parent 18e7188 commit 670836c
Show file tree
Hide file tree
Showing 25 changed files with 106 additions and 101 deletions.
2 changes: 1 addition & 1 deletion doc/code/qml_noise.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ noise-related metadata can also be supplied to construct a noise model using:
~NoiseModel

Each conditional in the ``model_map`` (and ``meas_map``) evaluates the gate operations
(and terminal measurments) in the quantum circuit based on some condition of its attributes
(and terminal measurements) in the quantum circuit based on some condition of its attributes
(e.g., type, parameters, wires, etc.) and uses the corresponding callable to apply the
noise operations, using the user-provided metadata (e.g., hardware topologies or relaxation
times), whenever the condition is true. A noise model, once built, can be attached
Expand Down
10 changes: 0 additions & 10 deletions doc/development/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,6 @@ Pending deprecations
- Deprecated in v0.40
- Will be removed in v0.41

- Deprecated in v0.39
- Will be removed in v0.40

* The ``QubitStateVector`` template is deprecated.
Instead, use ``StatePrep``.

- Deprecated in v0.39
- Will be removed in v0.40

* ``op.ops`` and ``op.coeffs`` for ``Sum`` and ``Prod`` will be removed in the future. Use
:meth:`~.Operator.terms` instead.

Expand All @@ -70,7 +61,6 @@ Pending deprecations
values with a bit string. In the future, it will no longer accepts strings as control values.

- Deprecated in v0.36
- Will be removed in v0.37

Completed removal of legacy operator arithmetic
-----------------------------------------------
Expand Down
18 changes: 10 additions & 8 deletions doc/development/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ For example:
This execute method works in tandem with the optional :meth:`Device.preprocess_transforms <pennylane.devices.Device.preprocess_transforms>`
and :meth:`Device.setup_execution_config`, described below in more detail. Preprocessing transforms
turns generic circuits into ones supported by the device, or raises an error if the circuit is invalid.
turns generic circuits into ones supported by the device or raises an error if the circuit is invalid.
Execution produces numerical results from those supported circuits.

In a more minimal example, for any initial batch of quantum tapes and a config object, we expect to be able to do:
Expand Down Expand Up @@ -376,7 +376,7 @@ Wires
Devices can now either:

1) Strictly use wires provided by the user on initialization: ``device(name, wires=wires)``
2) Infer the number and ordering of wires provided by the submitted circuit.
2) Infer the number and order of wires provided by the submitted circuit
3) Strictly require specific wire labels

Option 2 allows workflows to change the number and labeling of wires over time, but sometimes users want
Expand Down Expand Up @@ -434,18 +434,20 @@ The execution config stores two kinds of information:

Device options are any device specific options used to configure the behavior of an execution. For
example, ``default.qubit`` has ``max_workers``, ``rng``, and ``prng_key``. ``default.tensor`` has
``contract``, ``cutoff``, ``dtype``, ``method``, and ``max_bond_dim``. These options are often set
``contract``, ``contraction_optimizer``, ``cutoff``, ``c_dtype``, ``local_simplify``, ``method``, and ``max_bond_dim``. These options are often set
with default values on initialization. These values should be placed into the ``ExecutionConfig.device_options``
dictionary in :meth:`~.devices.Device.setup_execution_config`. Note that we do provide a default
implementation of this method, but you will most likely need to override it yourself.

>>> dev = qml.device('default.tensor', wires=2, max_bond_dim=4, contract="nonlocal", dtype=np.complex64)
>>> dev = qml.device('default.tensor', wires=2, max_bond_dim=4, contract="nonlocal", c_dtype=np.complex64)
>>> dev.setup_execution_config().device_options
{'contract': 'nonlocal',
'cutoff': 1.1920929e-07,
'dtype': numpy.complex64,
'method': 'mps',
'max_bond_dim': 4}
'contraction_optimizer': 'auto-hq',
'cutoff': None,
'c_dtype': numpy.complex64,
'local_simplify': 'ADCRS',
'max_bond_dim': 4,
'method': 'mps'}

Even if the property is stored as an attribute on the device, execution should pull the value of
these properties from the config instead of from the device instance. While not yet integrated at
Expand Down
8 changes: 4 additions & 4 deletions doc/releases/changelog-0.40.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@
True
```

* Devices that extends `qml.devices.Device` now has an optional class attribute `capabilities`
* A device that extends `qml.devices.Device` now has an optional class attribute `capabilities`
that is an instance of the `DeviceCapabilities` data class, constructed from the configuration
file if it exists. Otherwise, it is set to `None`.
[(#6433)](https://github.com/PennyLaneAI/pennylane/pull/6433)
Expand All @@ -184,7 +184,7 @@
```

* Default implementations of `Device.setup_execution_config` and `Device.preprocess_transforms`
are added to the device API for devices that provides a TOML configuration file and thus have
are added to the device API for devices that provide a TOML configuration file, thereby having
a `capabilities` property.
[(#6632)](https://github.com/PennyLaneAI/pennylane/pull/6632)
[(#6653)](https://github.com/PennyLaneAI/pennylane/pull/6653)
Expand Down Expand Up @@ -337,7 +337,7 @@
[(#6567)](https://github.com/PennyLaneAI/pennylane/pull/6567)

* The `diagonalize_measurements` transform no longer raises an error for unknown observables. Instead,
they are left undiagonalized, with the expectation that observable validation will catch any undiagonalized
they are left un-diagonalized, with the expectation that observable validation will catch any un-diagonalized
observables that are also unsupported by the device.
[(#6653)](https://github.com/PennyLaneAI/pennylane/pull/6653)

Expand All @@ -360,7 +360,7 @@

* `qml.execute` can now be used with `diff_method="best"`.
Classical cotransform information is now handled lazily by the workflow. Gradient method
validation and program setup is now handled inside of `qml.execute`, instead of in `QNode`.
validation and program setup are now handled inside of `qml.execute`, instead of in `QNode`.
[(#6716)](https://github.com/PennyLaneAI/pennylane/pull/6716)

* Added PyTree support for measurements in a circuit.
Expand Down
29 changes: 16 additions & 13 deletions pennylane/capture/base_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,27 +84,27 @@ def interpret_measurement(self, measurement):
Now the interpreter can be used to transform functions and jaxpr:
>>> qml.capture.enable()
>>> interpreter = SimplifyInterpreter()
>>> def f(x):
... qml.RX(x, 0)**2
... qml.adjoint(qml.Z(0))
... return qml.expval(qml.X(0) + qml.X(0))
>>> simplified_f = interpreter(f)
>>> print(qml.draw(simplified_f)(0.5)
>>> print(qml.draw(simplified_f)(0.5))
0: ──RX(1.00)──Z─┤ <2.00*X>
>>> jaxpr = jax.make_jaxpr(f)(0.5)
>>> interpreter.eval(jaxpr.jaxpr, [], 0.5)
[expval(2.0 * X(0))]
**Handling higher order primitives:**
Two main strategies exist for handling higher order primitives (primitives with jaxpr as metatdata).
1) Structure preserving. Tracing the execution preserves the higher order primitive.
2) Structure flattening. Tracing the execution eliminates the higher order primitive.
Two main strategies exist for handling higher order primitives (primitives with jaxpr as metadata).
The first one is structure preserving (tracing the execution preserves the higher order primitive),
and the second one is structure flattening (tracing the execution eliminates the higher order primitive).
Compilation transforms, like the above ``SimplifyInterpreter``, may prefer to handle higher order primitives
via a structure preserving method. After transforming the jaxpr, the `for_loop` still exists. This maintains
via a structure-preserving method. After transforming the jaxpr, the `for_loop` still exists. This maintains
the compact structure of the jaxpr and reduces the size of the program. This behavior is the default.
>>> def g(x):
Expand All @@ -117,23 +117,26 @@ def interpret_measurement(self, measurement):
>>> jax.make_jaxpr(interpreter(g))(0.5)
{ lambda ; a:f32[]. let
_:f32[] = for_loop[
jaxpr_body_fn={ lambda ; b:i32[] c:f32[]. let
args_slice=slice(0, None, None)
consts_slice=slice(0, 0, None)
jaxpr_body_fn={ lambda ; b:i32[] c:f32[]. let
d:f32[] = convert_element_type[new_dtype=float32 weak_type=True] b
e:f32[] = mul c d
_:AbstractOperator() = RX[n_wires=1] e 0
in (c,) }
n_consts=0
in (c,) }
] 0 3 1 1.0
f:AbstractOperator() = PauliZ[n_wires=1] 0
g:AbstractOperator() = SProd[_pauli_rep=4.0 * Z(0)] 4.0 f
h:AbstractMeasurement(n_wires=None) = expval_obs g
in (h,) }
in (h,) }
Accumulation transforms, like device execution or conversion to tapes, may need to flatten out
the higher order primitive to execute it.
.. code-block:: python
import copy
class AccumulateOps(PlxprInterpreter):
def __init__(self, ops=None):
Expand All @@ -152,14 +155,14 @@ def _(self, start, stop, step, *invals, jaxpr_body_fn, consts_slice, args_slice)
state = invals[args_slice]
for i in range(start, stop, step):
state = copy(self).eval(jaxpr_body_fn, consts, i, *state)
state = copy.copy(self).eval(jaxpr_body_fn, consts, i, *state)
return state
>>> @qml.for_loop(3)
... def loop(i, x):
... qml.RX(x, i)
... return x
>>> accumulator = AccumlateOps()
>>> accumulator = AccumulateOps()
>>> accumulator(loop)(0.5)
>>> accumulator.ops
[RX(0.5, wires=[0]), RX(0.5, wires=[1]), RX(0.5, wires=[2])]
Expand Down Expand Up @@ -222,7 +225,7 @@ def setup(self) -> None:
def cleanup(self) -> None:
"""Perform any final steps after iterating through all equations.
Blank by default, this method can clean up instance variables. Particularily,
Blank by default, this method can clean up instance variables. Particularly,
this method can be used to deallocate qubits and registers when converting to
a Catalyst variant jaxpr.
"""
Expand Down
2 changes: 1 addition & 1 deletion pennylane/debugging/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def snapshots(tape: QuantumScript) -> tuple[QuantumScriptBatch, PostprocessingFn
If tape splitting is carried out, the transform will be conservative about the wires that it includes in each tape.
So, if all operations preceding a snapshot in a 3-qubit circuit has been applied to only one wire,
the tape would only be looking at this wire. This can be overriden by the configuration of the execution device
the tape would only be looking at this wire. This can be overridden by the configuration of the execution device
and its nature.
Regardless of the transform's behaviour, the output is a dictionary where each key is either
Expand Down
1 change: 1 addition & 0 deletions pennylane/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
.. autosummary::
:toctree: api
capabilities
default_qubit
default_gaussian
default_mixed
Expand Down
4 changes: 2 additions & 2 deletions pennylane/devices/_qubit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,15 +634,15 @@ def statistics(
results = []

for m in measurements:
# TODO: Remove this when all overriden measurements support the `MeasurementProcess` class
# TODO: Remove this when all overridden measurements support the `MeasurementProcess` class
if isinstance(m.mv, list):
# MeasurementProcess stores information needed for processing if terminal measurement
# uses a list of mid-circuit measurement values
obs = m # pragma: no cover
else:
obs = m.obs or m.mv
obs = m if obs is None else obs
# Check if there is an overriden version of the measurement process
# Check if there is an overridden version of the measurement process
if method := getattr(self, self.measurement_map[type(m)], False):
if isinstance(m, MeasurementTransform):
result = method(tape=circuit)
Expand Down
58 changes: 32 additions & 26 deletions pennylane/devices/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,19 @@ class ExecutionCondition(Enum):

@dataclass
class OperatorProperties:
"""Information about support for each operation.
Attributes:
invertible (bool): Whether the adjoint of the operation is also supported.
controllable (bool): Whether the operation can be controlled.
differentiable (bool): Whether the operation is supported for device gradients.
conditions (list[ExecutionCondition]): Execution conditions that the operation must meet.
"""
"""Information about support for each operation."""

invertible: bool = False
"""Whether the adjoint of the operation is also supported."""

controllable: bool = False
"""Whether the operation can be controlled."""

differentiable: bool = False
"""Whether the operation is supported for device gradients."""

conditions: list[ExecutionCondition] = field(default_factory=list)
"""Execution conditions that the operation must meet."""

def __and__(self, other: "OperatorProperties") -> "OperatorProperties":
# Take the intersection of support but the union of constraints (conditions)
Expand Down Expand Up @@ -103,31 +103,37 @@ def _get_supported_base_op(op_name: str, op_dict: dict[str, OperatorProperties])

@dataclass
class DeviceCapabilities: # pylint: disable=too-many-instance-attributes
"""Capabilities of a quantum device.
Attributes:
operations: Operations natively supported by the backend device.
observables: Observables that the device can measure.
measurement_processes: List of measurement processes supported by the backend device.
qjit_compatible (bool): Whether the device is compatible with qjit.
runtime_code_generation (bool): Whether the device requires run time generation of the quantum circuit.
dynamic_qubit_management (bool): Whether the device supports dynamic qubit allocation/deallocation.
overlapping_observables (bool): Whether the device supports measuring overlapping observables on the same tape.
non_commuting_observables (bool): Whether the device supports measuring non-commuting observables on the same tape.
initial_state_prep (bool): Whether the device supports initial state preparation.
supported_mcm_methods (list[str]): List of supported methods of mid-circuit measurements.
"""
"""Capabilities of a quantum device."""

operations: dict[str, OperatorProperties] = field(default_factory=dict)
"""Operations natively supported by the backend device."""

observables: dict[str, OperatorProperties] = field(default_factory=dict)
"""Observables that the device can measure."""

measurement_processes: dict[str, list[ExecutionCondition]] = field(default_factory=dict)
"""List of measurement processes supported by the backend device."""

qjit_compatible: bool = False
"""Whether the device is compatible with qjit."""

runtime_code_generation: bool = False
"""Whether the device requires run time generation of the quantum circuit."""

dynamic_qubit_management: bool = False
"""Whether the device supports dynamic qubit allocation/deallocation."""

overlapping_observables: bool = True
"""Whether the device supports measuring overlapping observables on the same tape."""

non_commuting_observables: bool = False
"""Whether the device supports measuring non-commuting observables on the same tape."""

initial_state_prep: bool = False
"""Whether the device supports initial state preparation."""

supported_mcm_methods: list[str] = field(default_factory=list)
"""List of supported methods of mid-circuit measurements."""

def filter(self, finite_shots: bool) -> "DeviceCapabilities":
"""Returns the device capabilities conditioned on the given program features."""
Expand Down Expand Up @@ -162,8 +168,8 @@ def from_toml_file(cls, file_path: str, runtime_interface="pennylane") -> "Devic
Args:
file_path (str): The path to the TOML file.
runtime_interface (str): The runtime execution interface to get the capabilities for.
Acceptable values are "pennylane" and "qjit". Use "pennylane" for capabilities of
the device's implementation of `Device.execute`, and "qjit" for capabilities of
Acceptable values are ``"pennylane"`` and ``"qjit"``. Use ``"pennylane"`` for capabilities of
the device's implementation of `Device.execute`, and ``"qjit"`` for capabilities of
the runtime execution function used by a qjit-compiled workflow.
"""
Expand Down Expand Up @@ -356,7 +362,7 @@ def parse_toml_document(document: dict) -> DeviceCapabilities:
"""Parses a TOML document into a DeviceCapabilities object.
This function will ignore sections that are specific to either runtime interface, such as
"qjit.operators.gates". To include these sections, use :func:`update_device_capabilities`
``"qjit.operators.gates"``. To include these sections, use :func:`update_device_capabilities`
on the capabilities object returned from this function.
"""
Expand Down Expand Up @@ -429,7 +435,7 @@ def observable_stopping_condition(obs: qml.operation.Operator) -> bool:


def validate_mcm_method(capabilities: DeviceCapabilities, mcm_method: str, shots_present: bool):
"""Validates an MCM method against the device's capabilities.'"""
"""Validates an MCM method against the device's capabilities."""

if mcm_method is None or mcm_method == "deferred":
return # no need to validate if requested deferred or if no method is requested.
Expand Down
2 changes: 1 addition & 1 deletion pennylane/devices/default_clifford.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ def simulate(
tableau_simulator.do_circuit(stim_circuit)
global_phase = qml.GlobalPhase(qml.math.sum(op.data[0] for op in global_phase_ops))

# Perform measurments based on whether shots are provided
# Perform measurements based on whether shots are provided
if circuit.shots:
meas_results = self.measure_statistical(circuit, stim_circuit, seed=seed)
else:
Expand Down
2 changes: 1 addition & 1 deletion pennylane/devices/device_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ def execute(
>>> dev.execute([tape])
(array(1.0),)
If the script has multiple measurments, then the device should return a tuple of measurements.
If the script has multiple measurements, then the device should return a tuple of measurements.
>>> tape = qml.tape.QuantumTape(measurements=[qml.expval(qml.Z(0)), qml.probs(wires=(0,1))])
>>> tape.shape(dev)
Expand Down
4 changes: 2 additions & 2 deletions pennylane/devices/execution_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ class MCMConfig:
"""A class to store mid-circuit measurement configurations."""

mcm_method: Optional[str] = None
"""Which mid-circuit measurement strategy to use. Use ``"deferred"`` for the deferred
"""The mid-circuit measurement strategy to use. Use ``"deferred"`` for the deferred
measurements principle and ``"one-shot"`` if using finite shots to execute the circuit for
each shot separately. Any other value will be passed to the device, and the device is
expected to handle mid-circuit measurements using the requested method. If not specified,
the device will decide which method to use."""

postselect_mode: Optional[str] = None
"""How postselection is handled with finite-shots. If ``"hw-like"``, invalid shots will be
discarded and only results for valid shots will be returned. In this case, less samples
discarded and only results for valid shots will be returned. In this case, fewer samples
may be returned than the original number of shots. If ``"fill-shots"``, the returned samples
will be of the same size as the original number of shots. If not specified, the device will
decide which mode to use. Note that internally ``"pad-invalid-samples"`` is used internally
Expand Down
2 changes: 1 addition & 1 deletion pennylane/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def circuit():
``measurements=None``.
If an existing ``QuantumCircuit`` already contains measurements, ``from_qiskit``
will return those measurements, provided that they are not overriden as shown above.
will return those measurements, provided that they are not overridden as shown above.
These measurements can be used, e.g., for conditioning with
:func:`qml.cond() <~.cond>`, or simply included directly within the QNode's return:
Expand Down
Loading

0 comments on commit 670836c

Please sign in to comment.