pennylane icon indicating copy to clipboard operation
pennylane copied to clipboard

`default.mixed` acquires `supports_mid_measure` capability

Open vincentmr opened this issue 1 year ago • 14 comments

…ts the dynamic_one_shot transform.

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:

Description of the Change:

Benefits:

Possible Drawbacks:

Related GitHub Issues: [sc-67681]

vincentmr avatar Jul 05 '24 13:07 vincentmr

Thanks for getting this started @vincentmr ! :rocket:

My student @bnishanth16 found a good test case. The example below works:


dev = qml.device('default.mixed', wires=1, shots=100)

@qml.qnode(dev)
def test(): 
    qml.Hadamard(0)
    m = qml.measure(0)
    return qml.expval(qml.PauliZ(0))

However, if we add a reset after the measurement,


dev = qml.device('default.mixed', wires=1, shots=100)

@qml.qnode(dev)
def test(): 
    qml.Hadamard(0)
    m = qml.measure(0, reset=True)
    return qml.expval(qml.PauliZ(0))

we receive an error. The relevant part of the stacktrace is:

  File "/home/olivia/Code/pennylane/pennylane/devices/default_mixed.py", line 799, in _apply_operation_core
    wires = operation.wires
            ^^^^^^^^^^^^^^^
AttributeError: 'list' object has no attribute 'wires'

glassnotes avatar Jul 05 '24 23:07 glassnotes

Hi @glassnotes , thanks for pointing this out. There is another issue I need to fix, but today is release day and I've been busy as a release manager. I'll fix it asap.

vincentmr avatar Jul 08 '24 19:07 vincentmr

@glassnotes Hopefully works now. I've left some comments about the implementation, which is just done by analogy with the state-vector calculators. Not sure if everything is theoretically consistent though. In essence, when we meet an MCM in a tape, we randomly take a single sample, apply a Projector gate and renormalize (here by the trace of the DM). Run that n-shot times and average (or concatenate, etc.) the results. Does that sound right to you?

vincentmr avatar Jul 08 '24 21:07 vincentmr

@glassnotes Hopefully works now. I've left some comments about the implementation, which is just done by analogy with the state-vector calculators. Not sure if everything is theoretically consistent though. In essence, when we meet an MCM in a tape, we randomly take a single sample, apply a Projector gate and renormalize (here by the trace of the DM). Run that n-shot times and average (or concatenate, etc.) the results. Does that sound right to you?

Thanks @vincentmr , we'll give it a test today! And yes, the math checks out, given some projector $\Pi$ and input $\rho$, this sends the state to $\rho^\prime = \Pi \rho \Pi^\dagger$, then we divide by $\hbox{Tr}(\rho^\prime)$ (and the code aligns with this :100:).

glassnotes avatar Jul 09 '24 14:07 glassnotes

@vincentmr - with dynamic_one_shot support in default.mixed, will postselection work out of the box?

I ask because of this request on our forum: https://discuss.pennylane.ai/t/postselection-of-measurement-is-not-supported-on-the-default-mixed-device/4914

trbromley avatar Jul 23 '24 16:07 trbromley

will postselection work out of the box?

I think so. It is either handled in the transform post-processing (neglecting mismatched shots) or here.

vincentmr avatar Jul 23 '24 17:07 vincentmr

Bug spotted when using mcm_method="tree-traversal" with default.mixed. The code example below shows when using tree-traversal method with default.qubit, where all the qubit of a random circuit is mid-circuit measured and random circuit applied again and the expectation values are measured. When trying the same with default.mixed (without any noise) yields erroneous results.

Expected behaviour:

import pennylane as qml
import numpy as np

n_wires = 2
dev = qml.device("default.qubit", wires = n_wires, shots = 10**4)

@qml.qnode(dev, mcm_method="tree-traversal")
def func():
    # random circuit
    qml.RY(np.pi/3,0)
    qml.CNOT([0,1])

    # midcircuit measure all the qubits
    for k in range(n_wires):
      m = qml.measure(k, reset = True)

    #random circuit
    qml.RY(np.pi/3,0)
    qml.CNOT([0,1])

    return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)]

func()

Output: [0.5126, 0.5126]

Actual behaviour:

import pennylane as qml
import numpy as np

n_wires = 2
dev = qml.device("default.mixed", wires = n_wires, shots = 10**4)

@qml.qnode(dev, mcm_method="tree-traversal")
def func():
    # random circuit
    qml.RY(np.pi/3,0)
    qml.CNOT([0,1])

    # midcircuit measure all the qubits
    for k in range(n_wires):
      m = qml.measure(k, reset = True)

    #random circuit
    qml.RY(np.pi/3,0)
    qml.CNOT([0,1])

    return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)]

func()

Output: [(1, 1)] or [(0, 0)]

Additional information: Using @qml.dynamic_one_shot with default.mixed for 100 mid-circuit measurements in a 3-qubit calculation takes around 6 to 7 hours on a workstation (shots = 1000). Looking to use "tree-traversal" with mixed for reduced simulation time.

bnishanth16 avatar Jul 23 '24 20:07 bnishanth16

Thanks @bnishanth16 . This is just a prototype PR adding dynamic_one_shot support. mcm_method="tree-traversal" isn't expected to work, if this PR was to be merged it would raise an error but there are no guards here. I am glad to hear that you would like to see mcm_method="tree-traversal" enabled though, but that would take more time as the method isn't abstracted away from default.qubit at the moment.

I'm surprised that a 3-qubit calculation would take so long for the parameters you've provided. Could you please share the problematic script so I can see what's going on?

vincentmr avatar Jul 24 '24 12:07 vincentmr

Hi, thanks @vincentmr for the clarification regarding "tree-traversal".

I incorrectly mentioned that the circuit has 100 mcms which takes 6 to 7 hours. The circuit I'm working on is a dynamical one with 100 iterations, where the number of mcms increases by 1 after every iteration, starting from 1 mcm for the first iteration. So the total number of circuit implementations is 100 and the total mcm is 100*101/2 = 5050. This overall implementation takes the above-mentioned time. I apologize for the confusion.

The 3-qubit mixed circuit with 100 mcms takes around 2 mins with 1000 shots and 12 mins with 10^4 shots.

Code example of dynamical circuit:

import pennylane as qml
import numpy as np

n_wires = 3
dev = qml.device("default.mixed", wires = n_wires, shots = 10**3)
divs = 100
s_t = np.linspace(0,np.pi/3,divs)

@qml.dynamic_one_shot
@qml.qnode(dev)
def circuit(iter):
    for i in range(n_wires):
        qml.Hadamard(i)

    for j in range(iter):
        qml.measure(0,reset  = True)
        qml.RY(s_t[j],0)
        qml.RandomLayers(weights=np.array([[0.1, -2.1]]), ratio_imprim=0.78,wires=range(n_wires))

        for k in range(n_wires):
            qml.DepolarizingChannel(0.05,k)

    return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)]

# results = [circuit(i) for i in range(divs)] # iteratively increasing circ by 1 mcm 
results = [circuit(i) for i in [divs]] # final circuit with 100 mcm

results

bnishanth16 avatar Jul 24 '24 22:07 bnishanth16

@bnishanth16 Thanks for the feedback and numbers. Unfortunately dynamic_one_shot is somewhat slow w.r.t. the number of shots, which is why we developed tree-traversal. Alas, the last is not a transform or something of the sort at the moment so it would require more effort to port to default.mixed.

Could you let us know what is your end game (for this project)? For example, you need to run that 5050-mcm circuit with 1e7 shots. Or maybe just 1e3 shots, but 1000 different parametrization. And you need it done within 20 minutes. Etc. Maybe we could provide a quick way forward for your research, and that would help us prioritize features in the next quarters.

vincentmr avatar Jul 29 '24 14:07 vincentmr

@vincentmr Thanks for the reply. The end goal of the project is expected to have a higher number of mcms than the previous estimate but can be used within 1e4 shots. Computing this faster would be appreciated. Could you provide suggestions on externally parallelizing the number of shots by using limited dynamic shots for each parallel shot list? For example, 100 shots for 100 mcms takes seconds to compute and externally parallelizing it and taking an average over let's say 10 parallel shot lists, can give the results of 1e3 shot computation, maybe in less time. Would this speed up the computation? Could you suggest alternative ideas for externally parallelizing dynamic_one_shot in case this idea doesn't work out?

bnishanth16 avatar Aug 01 '24 21:08 bnishanth16

@vincentmr Thanks for the reply. The end goal of the project is expected to have a higher number of mcms than the previous estimate but can be used within 1e4 shots. Computing this faster would be appreciated. Could you provide suggestions on externally parallelizing the number of shots by using limited dynamic shots for each parallel shot list? For example, 100 shots for 100 mcms takes seconds to compute and externally parallelizing it and taking an average over let's say 10 parallel shot lists, can give the results of 1e3 shot computation, maybe in less time. Would this speed up the computation? Could you suggest alternative ideas for externally parallelizing dynamic_one_shot in case this idea doesn't work out?

To be clear, say you have a circuit with 100 (computational basis) mcms then there are 2 ** 100 possible end states to sample from. If the outcome probability distribution (across possible end states) is extremely balanced, then you're out of luck because you would need at least a sample on each and everyone requiring at least 2 ** 100 shots. At the other extreme, if the outcome probability distribution is concentrated on a single state then you would need much fewer shots. However, you still need more than 1 in general because sampling from that single state is itself stochastic in nature and requires more or less sampling depending on the state's probability distribution and measurement.

dynamic_one_shot is suboptimal since it takes a single sample at the end of each circuit execution; this is because it does not know that it might always end up in the same state. If we knew (or assumed) that, we could take all samples at once and save numerous circuit executions. Going halfway, we could also take 100 shots every time (for example), reducing the runtime by almost 100. Is that what you are suggesting?

An approach to this would be to use the option mcm_method="single-branch-statistics". This option explores a single branch and samples the prescribed number of shots. For the circuit above (with expval measurements), you could run any number of these circuits in parallel and combine all the expectation values as a weighted average (weights are the number of shots for each result).

If you want to sample from all branches, you could do the same with dynamic_one_shot - so run any number of these circuits in parallel and combine all the expectation values as a weighted average. This is not supported out of the box for now, but it might be if the feature is requested.

vincentmr avatar Aug 02 '24 13:08 vincentmr

@vincentmr Thanks a lot for the detailed explanation and apologies for the delayed response. This clarified the inner workings of mcm.

If you want to sample from all branches, you could do the same with dynamic_one_shot - so run any number of these circuits in parallel and combine all the expectation values as a weighted average. This is not supported out of the box for now, but it might be if the feature is requested.

If this makes mixed dynamic_one_shot faster, It is highly appreciated. We request this feature.

bnishanth16 avatar Aug 09 '24 22:08 bnishanth16

@vincentmr @bnishanth16 just a quick correction: mcm_method="single-branch-statistics" only works when using qjit.

mudit2812 avatar Aug 12 '24 13:08 mudit2812