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

Adding decomposition MPSPrep #6896

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open

Adding decomposition MPSPrep #6896

wants to merge 16 commits into from

Conversation

KetpuntoG
Copy link
Contributor

@KetpuntoG KetpuntoG commented Jan 28, 2025

Before submitting

Please complete the following checklist when submitting a PR:

  • All new features must include a unit test.
    If you've fixed a bug or added code that should be tested, add a test to the
    test directory!

  • All new functions and code must be clearly commented and documented.
    If you do make documentation changes, make sure that the docs build and
    render correctly by running make docs.

  • Ensure that the test suite passes, by running make test.

  • Add a new entry to the doc/releases/changelog-dev.md file, summarizing the
    change, and including a link back to the PR.

  • The PennyLane source code conforms to
    PEP8 standards.
    We check all of our code against Pylint.
    To lint modified files, simply pip install pylint, and then
    run pylint pennylane/path/to/file.py.

When all the above are checked, delete everything above the dashed
line and fill in the pull request template.


Context:

[SC-83733]
[SC-81348]
MPSPrep only works in "lightning.tensor". With this decomposition, it will work in any device

Description of the Change:

Benefits:

Possible Drawbacks:

Related GitHub Issues:

Copy link
Contributor

Hello. You may have forgotten to update the changelog!
Please edit doc/releases/changelog-dev.md with:

  • A one-to-two sentence description of the change. You may include a small working example for new features.
  • A link back to this PR.
  • Your name (or GitHub username) in the contributors section.

Copy link

codecov bot commented Jan 28, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.59%. Comparing base (04bfe4d) to head (4a1b932).
Report is 10 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6896      +/-   ##
==========================================
- Coverage   99.59%   99.59%   -0.01%     
==========================================
  Files         478      479       +1     
  Lines       45306    45362      +56     
==========================================
+ Hits        45124    45179      +55     
- Misses        182      183       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@albi3ro albi3ro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We worked hard to remove global random number generation from pennylane, and so I'd rather not have it go back in, especially with interfering with the user's choice of seed.

@albi3ro albi3ro dismissed their stale review January 29, 2025 18:41

global random generation has been removed. No longer blocking the PR.

Copy link
Member

@mlxd mlxd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @KetpuntoG
just a few fly-by questions from me

unitary[:, :k] = columns

# Complete the remaining columns using Gram-Schmidt
rng = np.random.default_rng(42)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be seeded? Won't this ensure that each and every call to this will use the same correlated RNG outputs for the same input?
If None it will pull entropy from the operating system's default source, which is likely a better source of randomness. Or, allowing the provision of a seed as a function input would be the best option, as this ensure control is at the caller's level. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to do it or qml.assert_valid complained that the decomposition did not match the operator in the queue (two different Unitaries were generated)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert valid in the tests? But we should be able to specify the seed to ensure the tests generate the same output? I'd expect numerical differences in the outcomes with generators that are seeded differently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually these values of the matrices are not used for anything, we generate them just to be able to put the matrix in QubitUnitary. Since it is also a private function, I think it is ok to set it in this case :) User does not need to know about it

pennylane/templates/state_preparations/state_prep_mps.py Outdated Show resolved Hide resolved
Copy link
Contributor

@obliviateandsurrender obliviateandsurrender left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @KetpuntoG! Just leaving the first set of comments and will take a deeper look once these are addressed.

Comment on lines 52 to 53
ortogonal_projection_matrix = qml.math.eye(d) - qml.math.dot(unitary, qml.math.conj(unitary.T))
new_columns = qml.math.dot(ortogonal_projection_matrix, new_columns)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we directly be able to use QR decomposition here? 🤔 I was trying something like this -

n, k = 8, 3 # number of rows, fixed given number of columns
vectors = random_orthonormal_vectors(n, k) # this function gives me random orthonormal vectors
randext = np.random.randn(n, n - k)
Q, _ = np.linalg.qr(np.hstack([vectors, randext]))

assert np.allclose(Q @ Q.T, np.eye(len(unitary))) and np.allclose(Q @ Q.T, Q.T @ Q)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks good but sometimes it changes the sign of columns, see the following example

Screenshot 2025-01-31 at 9 27 36 AM

Copy link
Contributor

@obliviateandsurrender obliviateandsurrender Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For fixing this sign flip, maybe this should work:

Q, R = np.linalg.qr(np.hstack([vectors, randext]))
Q *= np.sign(np.diag(R))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome 🤩

pennylane/templates/state_preparations/state_prep_mps.py Outdated Show resolved Hide resolved
pennylane/templates/state_preparations/state_prep_mps.py Outdated Show resolved Hide resolved

for column in Ai:

vector = qml.math.zeros(2**n_wires, like=mps[0])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought argument like should be given a string - "jax", "numpy", etc. Might be nice to check once.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not very well documented that argument 🤔
If this help, I see this also in other parts in PL:

Screenshot 2025-01-31 at 9 33 37 AM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
doing the latter is twice faster though. We can store the interface outside the loop and use that within the loop. It would also help with the line 265 check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After in person discussion, we keep it as it is right now (jit complains about the change)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After testing something like this should work -

interface, dtype = qml.math.get_interface(mps[0]), mps[0].dtype
vector = qml.math.zeros(2**n_wires, like=interface, dtype=dtype)

pennylane/templates/state_preparations/state_prep_mps.py Outdated Show resolved Hide resolved
pennylane/templates/state_preparations/state_prep_mps.py Outdated Show resolved Hide resolved
obliviateandsurrender

This comment was marked as duplicate.

pennylane/templates/state_preparations/state_prep_mps.py Outdated Show resolved Hide resolved
Comment on lines +42 to +43
work_wires (Sequence[int]): list of extra qubits needed in the decomposition. The bond dimension of the mps
is defined as ``2^len(work_wires)``. Default is ``None``
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we define this as the maximum permissible bond dimension of the provided MPS?

self.hyperparameters["input_wires"] = qml.wires.Wires(wires)

if work_wires:
self.hyperparameters["work_wires"] = qml.wires.Wires(work_wires)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we require a check that sufficient work wires have been provided?

Comment on lines +256 to +259
if i == 0:
Ai = Ai.reshape((1, *Ai.shape))
elif i == len(mps) - 1:
Ai = Ai.reshape((*Ai.shape, 1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we do this rank-2 to rank-3 promotion outside the loop itself to prevent checking these conditions for every iteration?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conditional not needed, agree.


for column in Ai:

vector = qml.math.zeros(2**n_wires, like=mps[0])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
doing the latter is twice faster though. We can store the interface outside the loop and use that within the loop. It would also help with the line 265 check.

pennylane/templates/state_preparations/state_prep_mps.py Outdated Show resolved Hide resolved
Copy link
Contributor

@austingmhuang austingmhuang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KetpuntoG Nice work, looks good.



Args:
mps (List[Array]): list of arrays of rank-3 and rank-2 tensors representing an MPS state as a
product of site matrices. See the usage details section for more information.

wires (Sequence[int]): wires that the template acts on
work_wires (Sequence[int]): list of extra qubits needed in the decomposition. The bond dimension of the mps
is defined as ``2^len(work_wires)``. Default is ``None``
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
is defined as ``2^len(work_wires)``. Default is ``None``
is defined as ``2^len(work_wires)``. Default is ``None``.



Args:
mps (List[Array]): list of arrays of rank-3 and rank-2 tensors representing an MPS state as a
product of site matrices. See the usage details section for more information.

wires (Sequence[int]): wires that the template acts on
work_wires (Sequence[int]): list of extra qubits needed in the decomposition. The bond dimension of the mps
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional?

Comment on lines +30 to +31
This operator is natively supported on the ``lightning.tensor`` device, designed to run MPS structures efficiently. For other devices, implementing this operation uses a gate-based decomposition which requires auxiliary qubits (via ``work_wires``) to prepare the state vector represented by the MPS in a quantum circuit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, is there a reference to this implementation using a gate-based decomposition?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see it is in the compute_decomposition function. not sure where it's preferred.

Comment on lines +186 to +189
hyperparameters = (
("wires", self.hyperparameters["input_wires"]),
("work_wires", self.hyperparameters["work_wires"]),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a dictionary so we don't have to do hyperparams_dict = dict(metadata)?

Comment on lines +216 to +222
filtered_hyperparameters = {
key: value for key, value in self.hyperparameters.items() if key != "input_wires"
}
return self.compute_decomposition(
self.parameters, wires=self.hyperparameters["input_wires"], **filtered_hyperparameters
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we store self.hyperparameters["input_wires"] in a variable, then simply del self.hyperparameters["input_wires"], and do **self.hyperparameters?


Args:
mps (List[Array]): list of arrays of rank-3 and rank-2 tensors representing an MPS state as a
product of site matrices. See the usage details section for more information.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
product of site matrices. See the usage details section for more information.
product of site matrices.

"""

if work_wires is None:
raise AssertionError(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise AssertionError(
raise ValueError(

IMO, AssertionError is more for things like:

try:
    assert work_wires is None
except AssertionError:
    ....

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what if not enough work_wires are provided? Be cautious here.

Comment on lines +256 to +259
if i == 0:
Ai = Ai.reshape((1, *Ai.shape))
elif i == len(mps) - 1:
Ai = Ai.reshape((*Ai.shape, 1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conditional not needed, agree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants