Source code for pprop.gates.base
"""
This module defines :class:`Gate`, the abstract base class for all quantum gates
in the Pauli propagation framework.
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List, Optional, Tuple
from numpy import integer, intp
from pennylane.operation import Operation
from ..pauli.op import PauliOp
from ..pauli.sentence import CoeffTerms, PauliDict
[docs]
class Gate(ABC):
"""
Abstract base class for all quantum gates.
Each concrete gate subclass stores a PennyLane operator instance for
circuit drawing and a Heisenberg evolution rule used during Pauli
propagation. The constructor validates that the number of wires and
the presence or absence of a parameter are consistent with the
PennyLane gate's expectations.
Parameters
----------
qml_gate : pennylane.operation.Operation
PennyLane gate *class* (not instance) corresponding to this gate.
The constructor instantiates it with a placeholder parameter value
of ``1`` (for parametrised gates) or without parameters (for
non-parametrised gates).
wires : list[int]
Qubit indices on which this gate acts.
parameter : float, int, optional
If it is np.intp or np.integer it represents the tndex of
:math:`\\theta` in the global parameter vector.
If it is float, it is actually an assigned value to the gate.
If it is None, the gate is non-parametrised.
Attributes
----------
qml_gate : pennylane.operation.Operation
Instantiated PennyLane operator, used for circuit drawing.
wires : list[int]
Qubit indices on which this gate acts.
parameter : int or None
Value of the parametrized gate if float,
index into the global parameter vector if int,
or ``None`` for non-parametrised gates.
Raises
------
ValueError
If the number of wires does not match the gate's requirement.
ValueError
If the gate expects more than one parameter (unsupported).
ValueError
If a parametrised gate is constructed without a ``parameter_index``.
ValueError
If a non-parametrised gate is constructed with a ``parameter_index``.
"""
def __init__(
self,
qml_gate: Operation,
wires: List[int],
parameter: Optional[int] = None,
) -> None:
# Instantiate the PennyLane gate with a placeholder value so we can
# query its metadata (num_wires, num_params, name).
self.qml_gate = (
qml_gate(1, wires=wires) if parameter is not None
else qml_gate(wires=wires)
)
self.wires = wires
self.parameter = parameter
# ------------------------------------------------------------------ #
# Validation #
# ------------------------------------------------------------------ #
# 1. Wire count must match the gate's requirements.
num_wires_expected = self.qml_gate.num_wires
if len(wires) != num_wires_expected:
raise ValueError(
f"{self.qml_gate.name} requires {num_wires_expected} wire(s), "
f"but {len(wires)} were given."
)
# 2. Only 0- or 1-parameter gates are supported.
if self.qml_gate.num_params > 1:
raise ValueError(
f"{self.qml_gate.name} expects more than 1 parameter; "
f"only 0- or 1-parameter gates are supported."
)
gate_has_param = self.qml_gate.num_params > 0
# 3. Parametrised gate must receive a parameter_index.
if gate_has_param and parameter is None:
raise ValueError(
f"{self.qml_gate.name} requires a parameter, "
f"but parameter is None."
)
# 4. Non-parametrised gate must not receive a parameter_index.
if not gate_has_param and parameter is not None:
raise ValueError(
f"{self.qml_gate.name} does not accept parameters, "
f"but parameter_index={parameter} was given."
)
[docs]
@abstractmethod
def evolve(self, word: Tuple[PauliOp, CoeffTerms], k1, k2) -> PauliDict:
"""
Heisenberg-evolve a Pauli word through this gate.
Computes :math:`G^\\dagger\\, P\\, G` for the Pauli word :math:`P`
encoded in ``word``, where :math:`G` is this gate. The result is
returned as a :class:`~pprop.pauli.sentence.PauliDict` mapping each
output Pauli word to its updated :data:`~pprop.pauli.sentence.CoeffTerms`.
Subclasses may return an empty :class:`~pprop.pauli.sentence.PauliDict`
to signal that the evolved word has been truncated by a cutoff.
Parameters
----------
word : tuple[PauliOp, CoeffTerms]
``(pauliop, coeff_terms)`` pair representing the Pauli word to evolve
and its current symbolic coefficient.
k1 : int or None
Pauli weight cutoff. Evolved words whose weight exceeds ``k1``
are discarded. ``None`` disables weight truncation.
k2 : int or None
Frequency cutoff. Evolved terms whose trigonometric frequency
exceeds ``k2`` are discarded. ``None`` disables frequency truncation.
Returns
-------
PauliDict
The evolved Pauli word(s) with updated coefficients. May be empty
if the result was truncated by ``k1`` or ``k2``.
"""
def __repr__(self) -> str:
"""
Return a concise string representation of the gate.
Returns
-------
str
``"GateName([wires])"`` for non-parametrised gates, or
``"GateName(θ_i, [wires])"`` for parametrised gates.
"""
if self.parameter is None:
return f"{self.qml_gate.name}({self.wires})"
elif isinstance(self.parameter, (integer, intp)):
return f"{self.qml_gate.name}({self.parameter}, {self.wires})"
else:
return f"{self.qml_gate.name}(θ_{self.parameter}, {self.wires})"