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

Add PrepSelPrep block encoding template #5739

Open
soranjh opened this issue May 24, 2024 · 4 comments
Open

Add PrepSelPrep block encoding template #5739

soranjh opened this issue May 24, 2024 · 4 comments
Labels
enhancement ✨ New feature or request

Comments

@soranjh
Copy link
Contributor

soranjh commented May 24, 2024

Feature details

⚠️ This issue is part of an internal assignment and not meant for external contributors.

A linear combination of unitaries (LCU) can be block-encoded using Prepare and Select operators. Adding an operation to PennyLane that implements this algorithm will facilitate block-encoding LCU operators. The operation can be used in a quantum circuit as

lcu = qml.dot([0.25, 0.75], [qml.Z(1), qml.X(0) @ qml.X(1)])

dev = qml.device('default.qubit')

@qml.qnode(dev)
def circuit():
    qml.PrepSelPrep(lcu, control = 0)  
    return qml.state()

Implementation

This demo provides details to construct the block encoding circuit.

The qml.PrepSelPrep operation should be implemented as a template and added to the subroutines module.

Requirements

  1. The qml.PrepSelPrep operation should correctly block-encode an LCU operation with positive and negative coefficients. Supporting imaginary coefficients is desired but not mandatory.

  2. The differentiability of the workflow should be tested with respect to the LCU elements with autograd and JAX. Optionally, the template should work with jax.jit.

@soranjh soranjh added the enhancement ✨ New feature or request label May 24, 2024
@soranjh soranjh changed the title Add PrepSelPrep block encoding template Add PrepSelPrep block encoding template May 24, 2024
@willjmax
Copy link
Contributor

I've implemented qml.PrepSelPrep as a template, and set up some tests inspired by the tests for qml.Select. It appears to be working correctly except for the differentiability tests.

I'd like some clarification. The function should be differentiable with respect to the LCU elements. In your example the LCU is built from qml.dot which returns a type pennylane.ops.op_math.sum.Sum, but autograd raises a type error saying that this type cannot be differentiated. Should qml.PrepSelPrep take some other type as input? or am I misunderstanding the requirement?

@soranjh
Copy link
Contributor Author

soranjh commented May 28, 2024

@willjmax Thanks for working on this. Could you please open your PR and ask the question there. Please tag me as reviewer so I can see the PR as soon as it is open.

@willjmax
Copy link
Contributor

See PR #5756

@soranjh
Copy link
Contributor Author

soranjh commented May 28, 2024

Thanks.

soranjh added a commit that referenced this issue Jul 3, 2024
This PR introduces a template implementing `qml.PrepSelPrep` to perform
a block encoding on a linear combination of unitaries. This is a work in
progress as it does not yet support autograd or JAX. It can be used in
the following way.

```
lcu = qml.dot([0.25, 0.75], [qml.Z(2), qml.X(1) @ qml.X(2)])

dev = qml.device('default.qubit')

@qml.qnode(dev)
def circuit():
    qml.PrepSelPrep(lcu, control = 0)  
    return qml.state()
```

Which is equivalent to

```
coeffs, unitaries = lcu.terms()
normalized_coeffs = np.sqrt(coeffs) / np.linalg.norm(np.sqrt(coeffs))

@qml.qnode(dev)
def circuit():
    qml.StatePrep(normalized_coeffs, wires=0)
    qml.Select(unitaries, control=0)
    qml.adjoint(qml.StatePrep(normalized_coeffs, wires=0))

```

The tests in `test_prepselprep.py` pass, and are inspired by similar
tests found in `test_select.py`. However, feedback on two tests would be
appreciated.

1) In the `assert_valid` test, within `_check_decomposition`, there is a
check to see if the operations in the decomposition match the operations
in the queue. I was able to get this test to pass by adding `with
qml.QueuingManager.stop_recording()` to the decomposition function. The
decomposition function calls `qml.Select` and `qml.StatePrep`, so my
best guess is that the operations were being added to the queue twice:
once in `qml.PrepSelPrep` and again in the two subroutines. I haven't
figured out how the queue works yet, so this could be totally wrong.

2) In `assert_valid` the `_check_pytree` test fails when JAX is
available, but passes otherwise.

### Differentiation
Here are two proposed autograd tests, each based off of the one found in
`test_select.py`, and both fail in different ways. These are not full
tests, but stripped down to the minimum required to produce the errors.

```
def test_autograd1():
    dev = qml.device("default.qubit")

    coeffs = pnp.array([1/2, 1/2], requires_grad=True)
    ops = [qml.Identity(1), qml.PauliZ(1)]
    lcu = qml.dot(coeffs, ops)

    @qml.qnode(dev)
    def circuit(coeffs, ops):
        lcu = qml.dot(coeffs, ops)
        qml.PrepSelPrep(lcu, control=0)
        return qml.expval(qml.Identity(1))

    qml.grad(circuit)(coeffs, ops)


def test_autograd2():
    dev = qml.device("default.qubit")

    coeffs = pnp.array([1/2, 1/2], requires_grad=True)
    ops = [qml.Identity(1), qml.PauliZ(1)]
    lcu = qml.dot(coeffs, ops)

    @qml.qnode(dev)
    def circuit(lcu):
        qml.PrepSelPrep(lcu, control=0)
        return qml.expval(qml.Identity(1))

    qml.grad(circuit)(lcu)
```

1) `test_autograd1` fails with the error `TypeError: loop of ufunc does
not support argument 0 of type ArrayBox which has no callable sqrt
method`. This is being caused within the decomposition function by the
line `normalized_coeffs = np.sqrt(coeffs) /
np.linalg.norm(np.sqrt(coeffs))`. The issue is that `coeffs` is a list
of autograd Arrayboxes and `np.sqrt` cannot be called on them. I think I
should be using the functions in `qml.math` as they will check the types
of the input and call the appropriate functions, but there is no
`qml.math.sqrt` to call.

2) `test_autograd2` passes, but with a warning since `lcu` is of type
`pennylane.ops.op_math.sum.Sum` and does not have a `requires_grad`
attribute. Attempting to force it to differentiate with
`qml.grad(circuit, argnum=0)` results in a type error since
`pennylane.ops.op_math.sum.Sum` is not differentiable.


**Related GitHub Issues:** #5739

---------

Co-authored-by: soranjh <[email protected]>
Co-authored-by: soranjh <[email protected]>
RenkeHuang pushed a commit to RenkeHuang/pennylane that referenced this issue Jul 11, 2024
This PR introduces a template implementing `qml.PrepSelPrep` to perform
a block encoding on a linear combination of unitaries. This is a work in
progress as it does not yet support autograd or JAX. It can be used in
the following way.

```
lcu = qml.dot([0.25, 0.75], [qml.Z(2), qml.X(1) @ qml.X(2)])

dev = qml.device('default.qubit')

@qml.qnode(dev)
def circuit():
    qml.PrepSelPrep(lcu, control = 0)
    return qml.state()
```

Which is equivalent to

```
coeffs, unitaries = lcu.terms()
normalized_coeffs = np.sqrt(coeffs) / np.linalg.norm(np.sqrt(coeffs))

@qml.qnode(dev)
def circuit():
    qml.StatePrep(normalized_coeffs, wires=0)
    qml.Select(unitaries, control=0)
    qml.adjoint(qml.StatePrep(normalized_coeffs, wires=0))

```

The tests in `test_prepselprep.py` pass, and are inspired by similar
tests found in `test_select.py`. However, feedback on two tests would be
appreciated.

1) In the `assert_valid` test, within `_check_decomposition`, there is a
check to see if the operations in the decomposition match the operations
in the queue. I was able to get this test to pass by adding `with
qml.QueuingManager.stop_recording()` to the decomposition function. The
decomposition function calls `qml.Select` and `qml.StatePrep`, so my
best guess is that the operations were being added to the queue twice:
once in `qml.PrepSelPrep` and again in the two subroutines. I haven't
figured out how the queue works yet, so this could be totally wrong.

2) In `assert_valid` the `_check_pytree` test fails when JAX is
available, but passes otherwise.

Here are two proposed autograd tests, each based off of the one found in
`test_select.py`, and both fail in different ways. These are not full
tests, but stripped down to the minimum required to produce the errors.

```
def test_autograd1():
    dev = qml.device("default.qubit")

    coeffs = pnp.array([1/2, 1/2], requires_grad=True)
    ops = [qml.Identity(1), qml.PauliZ(1)]
    lcu = qml.dot(coeffs, ops)

    @qml.qnode(dev)
    def circuit(coeffs, ops):
        lcu = qml.dot(coeffs, ops)
        qml.PrepSelPrep(lcu, control=0)
        return qml.expval(qml.Identity(1))

    qml.grad(circuit)(coeffs, ops)

def test_autograd2():
    dev = qml.device("default.qubit")

    coeffs = pnp.array([1/2, 1/2], requires_grad=True)
    ops = [qml.Identity(1), qml.PauliZ(1)]
    lcu = qml.dot(coeffs, ops)

    @qml.qnode(dev)
    def circuit(lcu):
        qml.PrepSelPrep(lcu, control=0)
        return qml.expval(qml.Identity(1))

    qml.grad(circuit)(lcu)
```

1) `test_autograd1` fails with the error `TypeError: loop of ufunc does
not support argument 0 of type ArrayBox which has no callable sqrt
method`. This is being caused within the decomposition function by the
line `normalized_coeffs = np.sqrt(coeffs) /
np.linalg.norm(np.sqrt(coeffs))`. The issue is that `coeffs` is a list
of autograd Arrayboxes and `np.sqrt` cannot be called on them. I think I
should be using the functions in `qml.math` as they will check the types
of the input and call the appropriate functions, but there is no
`qml.math.sqrt` to call.

2) `test_autograd2` passes, but with a warning since `lcu` is of type
`pennylane.ops.op_math.sum.Sum` and does not have a `requires_grad`
attribute. Attempting to force it to differentiate with
`qml.grad(circuit, argnum=0)` results in a type error since
`pennylane.ops.op_math.sum.Sum` is not differentiable.

**Related GitHub Issues:** PennyLaneAI#5739

---------

Co-authored-by: soranjh <[email protected]>
Co-authored-by: soranjh <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement ✨ New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants