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

Use native implementation for adjoints in (control) operations #1063

Merged
merged 32 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1b1b8c6
LQ
josephleekl Feb 11, 2025
291f04a
Auto update version from '0.41.0-dev19' to '0.41.0-dev21'
ringo-but-quantum Feb 11, 2025
c11395a
Merge branch 'master' into recursive-ctrl-adj
josephleekl Feb 11, 2025
97d1cec
format
josephleekl Feb 11, 2025
d9b9bff
elif
josephleekl Feb 11, 2025
f727448
add tests
josephleekl Feb 11, 2025
57a0a9e
update changelog
josephleekl Feb 11, 2025
1216338
update LK LG
josephleekl Feb 12, 2025
4791599
format
josephleekl Feb 12, 2025
190b9af
remove inv
josephleekl Feb 13, 2025
21ab151
no longer recursive
josephleekl Feb 20, 2025
66aeaed
update changelog
josephleekl Feb 20, 2025
ea8282a
fix matrix inverse
josephleekl Feb 21, 2025
530c3e8
format
josephleekl Feb 21, 2025
1b5a468
fix baseop
josephleekl Feb 21, 2025
d99c298
comments
josephleekl Feb 24, 2025
77b1fb7
Auto update version from '0.41.0-dev21' to '0.41.0-dev22'
ringo-but-quantum Feb 24, 2025
39b1ee4
[Capture] add execution_config kwarg to eval_jaxpr (#1067)
albi3ro Feb 24, 2025
07aee03
revert serialize
josephleekl Feb 25, 2025
19033cc
Merge branch 'master' into recursive-ctrl-adj
josephleekl Feb 25, 2025
947a3e7
update serializer
josephleekl Feb 25, 2025
7268072
Auto update version from '0.41.0-dev22' to '0.41.0-dev23'
ringo-but-quantum Feb 25, 2025
54059ed
Merge branch 'master' into recursive-ctrl-adj
josephleekl Feb 25, 2025
8575cad
update tests names
josephleekl Feb 25, 2025
7dc8e54
add qubit unitary tests
josephleekl Feb 25, 2025
e7dc6cd
update test
josephleekl Feb 25, 2025
2cd9dac
format
josephleekl Feb 25, 2025
e1ff2ad
ali/alfredo comments
josephleekl Feb 26, 2025
5284020
apply comments
josephleekl Feb 26, 2025
3daa7ea
Auto update version from '0.41.0-dev23' to '0.41.0-dev24'
ringo-but-quantum Feb 26, 2025
b0f5ca0
Merge branch 'master' into recursive-ctrl-adj
josephleekl Feb 26, 2025
a582803
Auto update version from '0.41.0-dev24' to '0.41.0-dev25'
ringo-but-quantum Feb 26, 2025
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
3 changes: 3 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

### Improvements

* Use native C++ kernels for controlled-adjoint and adjoint-controlled of supported operations.
[(#1063)](https://github.com/PennyLaneAI/pennylane-lightning/pull/1063)

* In Lightning-Tensor, allow `qml.MPSPrep` to accept an MPS with `len(MPS) = n_wires-1`.
[(#1064)](https://github.com/PennyLaneAI/pennylane-lightning/pull/1064)

Expand Down
71 changes: 51 additions & 20 deletions pennylane_lightning/core/_serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,8 +443,18 @@ def serialize_ops(self, tape: QuantumTape, wires_map: dict = None) -> Tuple[
uses_stateprep = False

def get_wires(operation, single_op):
if isinstance(operation, qml.ops.op_math.Controlled) and not isinstance(
operation,
# Serialize adjoint(op) and adjoint(ctrl(op))
if isinstance(operation, qml.ops.op_math.Adjoint):
inverse = True
op_base = operation.base
single_op_base = single_op.base
else:
inverse = False
op_base = operation
single_op_base = single_op

if isinstance(op_base, qml.ops.op_math.Controlled) and not isinstance(
op_base,
(
qml.CNOT,
qml.CY,
Expand All @@ -457,19 +467,41 @@ def get_wires(operation, single_op):
qml.CSWAP,
),
):
name = operation.base.name
wires_list = list(operation.target_wires)
controlled_wires_list = list(operation.control_wires)
control_values_list = operation.control_values
wires_list = list(op_base.target_wires)
controlled_wires_list = list(op_base.control_wires)
control_values_list = op_base.control_values
# Serialize ctrl(adjoint(op))
if isinstance(op_base.base, qml.ops.op_math.Adjoint):
ctrl_adjoint = True
name = op_base.base.base.name
else:
ctrl_adjoint = False
name = op_base.base.name

# Inside the controlled operation, if the base operation (of the adjoint)
# is supported natively, we apply the the base operation and invert the
# inverse flag; otherwise we apply the QubitUnitary of a matrix which
# contains the inverse and leave the inverse flag as is.
if not hasattr(self.sv_type, name):
single_op = QubitUnitary(matrix(single_op.base), single_op.base.wires)
name = single_op.name
single_op_base = QubitUnitary(
matrix(single_op_base.base), single_op_base.base.wires
)
name = single_op_base.name
else:
inverse ^= ctrl_adjoint
else:
name = single_op.name
wires_list = single_op.wires.tolist()
name = single_op_base.name
wires_list = single_op_base.wires.tolist()
controlled_wires_list = []
control_values_list = []
return single_op, name, list(wires_list), controlled_wires_list, control_values_list
return (
single_op_base,
name,
inverse,
list(wires_list),
controlled_wires_list,
control_values_list,
)

for operation in tape.operations:
if isinstance(operation, (BasisState, StatePrep)):
Expand All @@ -480,30 +512,29 @@ def get_wires(operation, single_op):
else:
op_list = [operation]

inverse = isinstance(operation, qml.ops.op_math.Adjoint)

for single_op in op_list:
(
single_op,
single_op_base,
name,
inverse,
wires_list,
controlled_wires_list,
controlled_values_list,
) = get_wires(operation, single_op)
inverses.append(inverse)
names.append(single_op.base.name if inverse else name)
names.append(name)
# QubitUnitary is a special case, it has a parameter which is not differentiable.
# We thus pass a dummy 0.0 parameter which will not be referenced
if isinstance(single_op, qml.QubitUnitary):
if isinstance(single_op_base, qml.QubitUnitary):
params.append([0.0])
mats.append(matrix(single_op))
mats.append(matrix(single_op_base))
else:
if hasattr(self.sv_type, single_op.base.name if inverse else name):
params.append(single_op.parameters)
if hasattr(self.sv_type, name):
params.append(single_op_base.parameters)
mats.append([])
else:
params.append([])
mats.append(matrix(single_op))
mats.append(matrix(single_op_base))

controlled_values.append(controlled_values_list)
controlled_wires.append(
Expand Down
4 changes: 3 additions & 1 deletion pennylane_lightning/core/_state_vector_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import numpy as np
from pennylane import BasisState, StatePrep
from pennylane.measurements import MidMeasureMP
from pennylane.ops import Controlled
from pennylane.tape import QuantumScript
from pennylane.wires import Wires

Expand Down Expand Up @@ -131,11 +132,12 @@ def _apply_basis_state(self, state, wires):
self._qubit_state.setBasisState(list(state), list(wires))

@abstractmethod
def _apply_lightning_controlled(self, operation):
def _apply_lightning_controlled(self, operation: Controlled, adjoint: bool):
"""Apply an arbitrary controlled operation to the state tensor.

Args:
operation (~pennylane.operation.Operation): controlled operation to apply
adjoint (bool): Apply the adjoint of the operation if True

Returns:
None
Expand Down
2 changes: 1 addition & 1 deletion pennylane_lightning/core/_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-dev24"
__version__ = "0.41.0-dev25"
34 changes: 20 additions & 14 deletions pennylane_lightning/lightning_gpu/_state_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,34 +229,39 @@
# set the state vector on GPU with provided state and their corresponding wires
self._qubit_state.setStateVector(state, list(device_wires), use_async)

def _apply_lightning_controlled(self, operation):
def _apply_lightning_controlled(self, operation, adjoint):
"""Apply an arbitrary controlled operation to the state tensor.

Args:
operation (~pennylane.operation.Operation): controlled operation to apply
adjoint (bool): Apply the adjoint of the operation if True

Returns:
None
"""
state = self.state_vector

basename = operation.base.name
method = getattr(state, f"{basename}", None)
if isinstance(operation.base, Adjoint):
base_operation = operation.base.base
adjoint = not adjoint

Check warning on line 246 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L244-L246

Added lines #L244 - L246 were not covered by tests
else:
base_operation = operation.base

Check warning on line 248 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L248

Added line #L248 was not covered by tests

method = getattr(state, f"{base_operation.name}", None)

Check warning on line 250 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L250

Added line #L250 was not covered by tests
control_wires = list(operation.control_wires)
control_values = operation.control_values
target_wires = list(operation.target_wires)
if method: # apply n-controlled specialized gate
inv = False
param = operation.parameters
method(control_wires, control_values, target_wires, inv, param)
method(control_wires, control_values, target_wires, adjoint, param)

Check warning on line 256 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L256

Added line #L256 was not covered by tests
else: # apply gate as an n-controlled matrix
method = getattr(state, "applyControlledMatrix")
method(
qml.matrix(operation.base),
qml.matrix(base_operation),
control_wires,
control_values,
target_wires,
False,
adjoint,
)

def _apply_lightning_midmeasure(
Expand Down Expand Up @@ -300,6 +305,7 @@
postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to
keep the same number of shots. Default is ``None``.


Returns:
None
"""
Expand All @@ -311,11 +317,12 @@
if isinstance(operation, qml.Identity):
continue
if isinstance(operation, Adjoint):
name = operation.base.name
op_adjoint_base = operation.base

Check warning on line 320 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L320

Added line #L320 was not covered by tests
invert_param = True
else:
name = operation.name
op_adjoint_base = operation

Check warning on line 323 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L323

Added line #L323 was not covered by tests
invert_param = False
name = op_adjoint_base.name

Check warning on line 325 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L325

Added line #L325 was not covered by tests
method = getattr(state, name, None)
wires = list(operation.wires)

Expand All @@ -330,13 +337,13 @@
param = operation.parameters
method(wires, invert_param, param)
elif (
isinstance(operation, qml.ops.Controlled) and not self._mpi_handler.use_mpi
isinstance(op_adjoint_base, qml.ops.Controlled) and not self._mpi_handler.use_mpi
): # MPI backend does not have native controlled gates support
self._apply_lightning_controlled(operation)
self._apply_lightning_controlled(op_adjoint_base, invert_param)

Check warning on line 342 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L342

Added line #L342 was not covered by tests
elif (
self._mpi_handler.use_mpi
and isinstance(operation, qml.ops.Controlled)
and isinstance(operation.base, qml.GlobalPhase)
and isinstance(op_adjoint_base, qml.ops.Controlled)
and isinstance(op_adjoint_base.base, qml.GlobalPhase)
):
# TODO: To move this line to the _apply_lightning_controlled method once the MPI backend supports controlled gates natively
raise DeviceError(
Expand All @@ -348,7 +355,6 @@
except AttributeError: # pragma: no cover
# To support older versions of PL
mat = operation.matrix

r_dtype = np.float32 if self.dtype == np.complex64 else np.float64
param = (
[[r_dtype(operation.hash)]]
Expand Down
33 changes: 19 additions & 14 deletions pennylane_lightning/lightning_kokkos/_state_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,34 +181,39 @@ def _apply_state_vector(self, state, device_wires: Wires):
# This operate on device
self._qubit_state.setStateVector(state, list(device_wires))

def _apply_lightning_controlled(self, operation):
def _apply_lightning_controlled(self, operation, adjoint):
"""Apply an arbitrary controlled operation to the state tensor.

Args:
operation (~pennylane.operation.Operation): controlled operation to apply
adjoint (bool): Apply the adjoint of the operation if True

Returns:
None
"""
state = self.state_vector

basename = operation.base.name
method = getattr(state, f"{basename}", None)
if isinstance(operation.base, Adjoint):
base_operation = operation.base.base
adjoint = not adjoint
else:
base_operation = operation.base

method = getattr(state, f"{base_operation.name}", None)
control_wires = list(operation.control_wires)
control_values = operation.control_values
target_wires = list(operation.target_wires)
inv = False # TODO: update to use recursive _apply_lightning to handle nested adjoint/ctrl
if method is not None: # apply n-controlled specialized gate
param = operation.parameters
method(control_wires, control_values, target_wires, inv, param)
method(control_wires, control_values, target_wires, adjoint, param)
else: # apply gate as an n-controlled matrix
method = getattr(state, "applyControlledMatrix")
method(
qml.matrix(operation.base),
qml.matrix(base_operation),
control_wires,
control_values,
target_wires,
inv,
adjoint,
)

def _apply_lightning_midmeasure(
Expand Down Expand Up @@ -262,11 +267,12 @@ def _apply_lightning(
if isinstance(operation, qml.Identity):
continue
if isinstance(operation, Adjoint):
name = operation.base.name
op_adjoint_base = operation.base
invert_param = True
else:
name = operation.name
op_adjoint_base = operation
invert_param = False
name = op_adjoint_base.name
method = getattr(state, name, None)
wires = list(operation.wires)

Expand All @@ -279,18 +285,17 @@ def _apply_lightning(
)
elif isinstance(operation, qml.PauliRot):
method = getattr(state, "applyPauliRot")
# pylint: disable=protected-access
paulis = operation._hyperparameters[
paulis = operation._hyperparameters[ # pylint: disable=protected-access
"pauli_word"
] # pylint: disable=protected-access
]
wires = [i for i, w in zip(wires, paulis) if w != "I"]
word = "".join(p for p in paulis if p != "I")
method(wires, invert_param, operation.parameters, word)
elif method is not None: # apply specialized gate
param = operation.parameters
method(wires, invert_param, param)
elif isinstance(operation, qml.ops.Controlled): # apply n-controlled gate
self._apply_lightning_controlled(operation)
elif isinstance(op_adjoint_base, qml.ops.Controlled): # apply n-controlled gate
self._apply_lightning_controlled(op_adjoint_base, invert_param)
else: # apply gate as a matrix
# Inverse can be set to False since qml.matrix(operation) is already in
# inverted form
Expand Down
Loading
Loading