Bug in QubitStateVector Tolerance

I’m trying to run experiments with randomly-generated states, but I’m noticing some strange behavior with the tolerance allowed when setting the state with QubitStateVector. For instance, if I generate a state with all zeros except the first entry, it seems to tolerate a relatively large amount of error in normalization. But if I generate a random state by sampling a normal distribution and normalizing it, it won’t accept it even if the normalization error is just extremely tiny numerical error.

What’s the source of this inconsistency, and is there any way to get around it? I will need to input both random states and computed ground states for my experiments, and the ground states I get by computing eigenvectors have the same issue.

Here’s some example code illustrating this:

import torch
import pennylane as qml
import numpy as np

device = qml.device("default.qubit", wires=3)
num_qubits = 3

state_1 = torch.zeros((2 ** num_qubits,), dtype=torch.cdouble)
state_1[0] = 1.0 - 1.j * 1.0e-4
print(torch.sum(state_1 ** 2))

state_2 = torch.normal(0, 1, (2 ** num_qubits,), dtype=torch.cdouble) + 1.j * torch.normal(0, 1, (2 ** num_qubits,), dtype=torch.cdouble)
state_2 /= torch.sqrt(torch.inner(state_2, state_2))
print(torch.sum(state_2 ** 2))

@qml.qnode(device)
def circuit(state):
    qml.QubitStateVector(state, wires=list(range(num_qubits)))
    return qml.probs()

circuit(state_1)
circuit(state_2)

And here’s the output I get:

tensor(1.0000-0.0002j, dtype=torch.complex128)
tensor(1.0000+0.j, dtype=torch.complex128)
Traceback (most recent call last):
  File "/home/lucastecot/development/quantum_optimization/sandbox.py", line 31, in <module>
    circuit(state_2)
  File "/home/lucastecot/miniconda3/envs/quantum_gen/lib/python3.10/site-packages/pennylane/qnode.py", line 976, in __call__
    self.construct(args, kwargs)
  File "/home/lucastecot/miniconda3/envs/quantum_gen/lib/python3.10/site-packages/pennylane/qnode.py", line 862, in construct
    self._qfunc_output = self.func(*args, **kwargs)
  File "/home/lucastecot/development/quantum_optimization/sandbox.py", line 27, in circuit
    qml.QubitStateVector(state, wires=list(range(num_qubits)))
  File "/home/lucastecot/miniconda3/envs/quantum_gen/lib/python3.10/site-packages/pennylane/ops/qubit/state_preparation.py", line 182, in __init__
    raise ValueError("Sum of amplitudes-squared does not equal one.")
ValueError: Sum of amplitudes-squared does not equal one.

Here’s the output of qml.about()

Name: PennyLane
Version: 0.34.0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/PennyLaneAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /home/lucastecot/miniconda3/envs/quantum_gen/lib/python3.10/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane-Lightning

Platform info:           Linux-3.10.0-957.5.1.el7.x86_64-x86_64-with-glibc2.17
Python version:          3.10.11
Numpy version:           1.24.3
Scipy version:           1.10.1
Installed devices:
- default.gaussian (PennyLane-0.34.0)
- default.mixed (PennyLane-0.34.0)
- default.qubit (PennyLane-0.34.0)
- default.qubit.autograd (PennyLane-0.34.0)
- default.qubit.jax (PennyLane-0.34.0)
- default.qubit.legacy (PennyLane-0.34.0)
- default.qubit.tf (PennyLane-0.34.0)
- default.qubit.torch (PennyLane-0.34.0)
- default.qutrit (PennyLane-0.34.0)
- null.qubit (PennyLane-0.34.0)
- lightning.qubit (PennyLane-Lightning-0.34.0)

Hey @Lucas_Tecot!

Interesting :thinking:… I’ll get back to you on why QubitStateVector might be doing this. But, a temporary workaround would be to use amplitude embedding:

import torch
import pennylane as qml
import numpy as np

device = qml.device("default.qubit", wires=3)
num_qubits = 3

state_1 = torch.zeros((2 ** num_qubits,), dtype=torch.cdouble)
state_1[0] = 1.0 - 1.j * 1.0e-4
print(torch.sum(state_1 ** 2))

state_2 = torch.normal(0, 1, (2 ** num_qubits,), dtype=torch.cdouble) + 1.j * torch.normal(0, 1, (2 ** num_qubits,), dtype=torch.cdouble)
state_2 /= torch.sqrt(torch.inner(state_2, state_2))
print(torch.sum(state_2 ** 2))

@qml.qnode(device)
def circuit(state):
    qml.AmplitudeEmbedding(features=state, wires=list(range(num_qubits)), normalize=True)
    return qml.probs()

circuit(state_1)
circuit(state_2)
tensor(1.0000-0.0002j, dtype=torch.complex128)
tensor(1.0000+4.8572e-17j, dtype=torch.complex128)
tensor([0.3989, 0.0696, 0.1888, 0.0347, 0.1705, 0.0516, 0.0455, 0.0403],
       dtype=torch.float64)

I’ll get back to you!

Oh! I just realized. You need to conj something in state2:

state_2 /= torch.sqrt(torch.inner(state_2, torch.conj(state_2)))

Then everything works with QubitStateVector :slight_smile:

Oops, I had a feeling I was maybe normalizing incorrectly! Thanks for pointing that out, it works for me too now.

1 Like

awesome! Glad it was a simple fix :slight_smile: