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

Fix issue when using a wire subset with BasisState #61

Merged
merged 12 commits into from
May 27, 2021

Conversation

thisac
Copy link
Contributor

@thisac thisac commented May 26, 2021

Fixes an issue when calling BasisState when using a subset of the device wires.

The following now works:

dev = qml.device("cirq.simulator", wires=2)

@qml.qnode(dev)
def circuit():
    qml.BasisState([1], wires=1)
    return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))

circuit()

Previously it raised
DeviceError: For BasisState, the state has to be specified for the correct number of qubits. Got a state for 1 qubits, expected 2.

and now it should output the correct [1, -1].

@thisac thisac requested review from josh146 and antalszava May 26, 2021 16:06
@thisac thisac self-assigned this May 26, 2021
@codecov
Copy link

codecov bot commented May 26, 2021

Codecov Report

Merging #61 (ff90b90) into master (14f421a) will increase coverage by 0.03%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #61      +/-   ##
==========================================
+ Coverage   99.10%   99.14%   +0.03%     
==========================================
  Files           8        8              
  Lines         334      349      +15     
==========================================
+ Hits          331      346      +15     
  Misses          3        3              
Impacted Files Coverage Δ
pennylane_cirq/simulator_device.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 14f421a...ff90b90. Read the comment docs.

Comment on lines 136 to 139
try:
wires = self.map_wires(qubit_state_vector_operation.wires)
except WireError:
wires = qubit_state_vector_operation.wires
Copy link
Contributor Author

@thisac thisac May 26, 2021

Choose a reason for hiding this comment

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

I'm not sure why this is, but there are cases when QubitStateVector is called on more wires than the device has. E.g., where it's called on wires [0, 1] but the device only has 1 wire, as in the following test.

def test_var_single_wire_no_parameters(
    self, simulator_device_1_wire, tol, operation, input, expected_output
):
    """Tests that variances are properly calculated for single-wire observables without parameters."""

    op = operation(0, do_queue=False)

    simulator_device_1_wire.reset()
    simulator_device_1_wire.apply(
        [qml.QubitStateVector(np.array(input), wires=[0, 1])],
        rotations=op.diagonalizing_gates(),
    )

This causes issues with map_wires (thus the try-except) and also the _expand_state below (thus the if len(wires) < self.num_wires ... instead of if len(wires) != self.num_wires ..., as I first wrote).

Copy link
Member

Choose a reason for hiding this comment

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

@thisac I would consider this a bug in the tests :) I suggest:

  • removing the try-except here
  • updating the test to use a 2 wire device

Copy link
Member

@josh146 josh146 left a comment

Choose a reason for hiding this comment

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

I did a quick skim through, looks good so far @thisac! I haven't done a proper review yet, as I noticed there are no tests (yet) to ensure that this bug is fixed

Comment on lines 136 to 139
try:
wires = self.map_wires(qubit_state_vector_operation.wires)
except WireError:
wires = qubit_state_vector_operation.wires
Copy link
Member

Choose a reason for hiding this comment

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

@thisac I would consider this a bug in the tests :) I suggest:

  • removing the try-except here
  • updating the test to use a 2 wire device

@thisac thisac requested a review from josh146 May 26, 2021 20:03
Copy link
Member

@josh146 josh146 left a comment

Choose a reason for hiding this comment

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

Thanks @thisac! Tests look very nice.

Only suggestion is to use standard NumPy rather than the device static methods.

Comment on lines 106 to 107
self._initial_state = np.zeros(2 ** len(self.qubits), dtype=np.complex64)
basis_state_idx = np.sum(2 ** np.argwhere(np.flip(basis_state_array) == 1))
Copy link
Member

Choose a reason for hiding this comment

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

Oh, this is a permutation of converting from base2 to base10 that I hadn't seen before!

Interestingly, however, this seems to be a rare case where the native Python approach is still faster:

>>> bs = np.array([1, 1, 0, 1, 1, 0, 0, 1, 1])
>>> int("".join(str(i) for i in bs), 2)
435
>>> np.sum(2 ** np.argwhere(np.flip(bs) == 1))
435
>>> %timeit np.sum(2 ** np.argwhere(np.flip(bs) == 1))
41.4 µs ± 4.82 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit int("".join(str(i) for i in bs), 2)
14.4 µs ± 489 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting! I feel like the Python solution is somewhat cleaner. The time spent there is almost all in the "".join while the NumPy solution spends a significant amounts of time in np.argwhere, 2 ** and np.sum. I guess there's just more that's going on there. 🤔

Comment on lines 121 to 124
state_vector = self._scatter(ravelled_indices, state_vector, [2 ** self.num_wires])
state_vector = self._reshape(state_vector, [2] * self.num_wires)
state_vector = self._asarray(state_vector, dtype=self.C_DTYPE)

Copy link
Member

Choose a reason for hiding this comment

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

Same comment here as over on the Qulacs PR, recommend changing this to simply use native NumPy functions :)

dev = SimulatorDevice(device_wires)
res = dev._expand_state(state, op_wires)

assert np.allclose(res, expected, **tol)
Copy link
Member

Choose a reason for hiding this comment

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

great tests!

@thisac thisac merged commit 6fe3a57 into master May 27, 2021
@thisac thisac deleted the fix-using-wire-subset branch May 27, 2021 17:54
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.

2 participants