quantumlib / Cirq

A Python framework for creating, editing, and invoking Noisy Intermediate Scale Quantum (NISQ) circuits.
Apache License 2.0
4.24k stars 1.01k forks source link

cirq entanglement issue report #6484

Closed marvinthemouse closed 6 months ago

marvinthemouse commented 6 months ago

Description of the issue

From what I've read, the circuit below is intended to demonstrate entanglement, using cirq.

Circuit: (3, 4): ───X───H───@───M('q0A')─ | (3, 5):────X──────X───M('q1A')─

From creating/running the circuit, I can see it demonstrates qubits collapsing to opposing states.

What I expect is to see entanglement further demonstrated by another operation being added that changes the state of one of the two qubits, after the CNOT gate, which automatically affects the other qubit.

I have tried achieving this by extending the circuit above, in the way you see below.

Circuit: (3, 4): ───X───H───@───M('q0A')───WaitGate(Duration(0))───M('q0B')─ | (3, 5):────X──────X───M('q1A')───X────────────M('q1B')─

The NOT gate against (3, 5), following M('q1A'), is my attempt to see entanglement force (3, 4) to automatically change state.

After running the second circuit, I do not see entanglement, as the NOT gate only results in the state of (3, 5) being changed.

The measurements below are typical of what I see, running the second circuit; please note, I have run this circuit with varying repetitions and the result is (predominantly) the same.

Measurements: q0A=1 q1A=0 q0B=1 q1B=1

Please note, the WaitGate is only included for moment alignment purposes within the circuit.

The following circuit was tried too, thinking the measurements were perhaps having a negative impact, but the result was the same as above, in that only one qubit changed its state, not both.

Circuit: (3, 4): ───X───H───@───WaitGate(Duration(0))───M('q1A')─── │ (3, 5): ───X───────X────────────────M('q1B')───

Measurements: q1A: 0=1, 1=0 q1B: 0=1, 1=0

How to reproduce the issue I expect running the code below will reproduce the issue.

import random
import numpy as np
import cirq
from pip._vendor.chardet import resultdict

import cirq_google
import qsimcirq

# Choose a processor ("weber" or "rainbow")
processor_id = "weber"

# Load the median device noise calibration for the selected processor
cal = cirq_google.engine.load_median_device_calibration(processor_id)

# Create the noise properties object
noise_props = cirq_google.noise_properties_from_calibration(cal)

# Create a noise model from the noise properties
noise_model = cirq_google.NoiseModelFromGoogleNoiseProperties(noise_props)

# Prepare a qsim simulator using the noise model
sim = qsimcirq.QSimSimulator(noise=noise_model)

# Define qubits using GridQubit  
q0 = cirq.GridQubit(3,4)
q1 = cirq.GridQubit(3,5)

shape=(2,2,2)
cnt = np.zeros(shape)

# function to capture results
def capture_result(rep, pnt, stg, val):
    swtch = {"A":0, "B":1}
    swsg = swtch[stg]
    cnt[pnt][swsg][val] += 1

# function to output counts
def output_cnt():
    swtch2 = {0:"A", 1:"B"}

    #Print the results
    print("\nResults:")

    for j in range(2): # point
        for k in range(2): # switch
            swsg2 = swtch2[k]
            print(f"q{j}{swsg2}: 0={cnt[j][k][0]}, 1={cnt[j][k][1]}")

# Create a circuit
circuit = cirq.Circuit()

# Apply the X-Pauli gate to each qubit
circuit.append([cirq.X(q0), cirq.X(q1)])

# Apply the Hadamard gate to first qubit 
circuit.append([cirq.H(q0)])

circuit.append([cirq.CNOT(q0, q1)], strategy=cirq.InsertStrategy.NEW)

#Measure both qubits
circuit.append([cirq.measure(q0, key='q0A')])
circuit.append([cirq.measure(q1, key='q0B')])

# Make a change to q1, to see if q0 shows a corresponding change
circuit.append([cirq.X(q1)]) # UNCOMMENT THE START OF THIS LINE
circuit.append([cirq.wait(q0,millis=0)])

#Measure both qubits
circuit.append([cirq.measure(q0, key='q1A')])
circuit.append([cirq.measure(q1, key='q1B')])

#Print the Circuit
print("Circuit:")
print(circuit,"\n")

#Print the Measurements
print("Measurements:")

for i in range(1 ):

    results = sim.run(circuit, repetitions=2)

    for k, measurements in enumerate(results.records["q0A"][:,-1,:]):
        capture_result(k,0,'A',measurements)

    for k, measurements in enumerate(results.records["q0B"][:,-1,:]):
        capture_result(k,0,'B',measurements)

    for k, measurements in enumerate(results.records["q1A"][:,-1,:]):
        capture_result(k,1,'A',measurements)

    for k, measurements in enumerate(results.records["q1B"][:,-1,:]):
        capture_result(k,1,'B',measurements)

output_cnt()
There are no logs to report, as the code runs without crashing.

The software versions used are as follows:

• Cirq version=1.3.0 • Python version= 3.12.1 • Run on a PC running Windows 10

Yamid07 commented 6 months ago

Continuous IntegrationP

NoureldinYosri commented 6 months ago

@marvinthemouse measurements collapse entanglment. so for the first circuit

(3, 4): ───X───H───@───M('q0A')───WaitGate(Duration(0))───M('q1A')───
                   │
(3, 5): ───X───────X───M('q0B')───X───────────────────────M('q1B')─── 

the qubits become free from one another after the measurements M('q0A') and M('q0B') and changing one doesn't affect the other, in fact only one of them is enough to collapse the entanglement.

As for the second circuit

(3, 4): ───X───H───@───M('q1A')───
                   │
(3, 5): ───X───────X───M('q1B')─── 

Before doing measurement the state of qubits is an entanglement $\frac{\ket{01} - \ket{10}}{\sqrt{2}}$ so the result of the measurement is equally likely to be either $q{3,4}=0, q{4,5}=1$ or $q{3,4}=1, q{4,5}=0$ which is one you get with enough repetitions

>>> results = sim.run(circuit, repetitions=10)
>>> results
cirq.ResultDict(params=cirq.ParamResolver({}), records={'q1B': np.array([[[1]], [[1]], [[0]], [[1]], [[1]], [[1]], [[1]], [[0]], [[0]], [[1]]], dtype=np.dtype('int64')), 'q1A': np.array([[[0]], [[0]], [[1]], [[0]], [[0]], [[0]], [[0]], [[0]], [[0]], [[0]]], dtype=np.dtype('int64'))})
marvinthemouse commented 6 months ago

Thanks for your feedback.

With a better understanding, I've since undertaken further tests.

I now see entanglement measured 80-100% of the time (averaging around 90%) with repetitions from 10 up to 5,000,000.

These results are from using the cicuit below:

Circuit: (3, 4): ───X───H───@───WaitGate(Duration(0))───M('q1A')─── │ (3, 5): ───X───────X───X────────────M('q1B')───

cirq.Circuit([ cirq.Moment( cirq.X(cirq.GridQubit(3, 4)), cirq.X(cirq.GridQubit(3, 5)), ), cirq.Moment( cirq.H(cirq.GridQubit(3, 4)), ), cirq.Moment( cirq.CNOT(cirq.GridQubit(3, 4), cirq.GridQubit(3, 5)), ), cirq.Moment( cirq.X(cirq.GridQubit(3, 5)), cirq.WaitGate(cirq.Duration(millis=0)).on(cirq.GridQubit(3, 4)), ), cirq.Moment( cirq.measure(cirq.GridQubit(3, 4), key=cirq.MeasurementKey(name='q1A')), cirq.measure(cirq.GridQubit(3, 5), key=cirq.MeasurementKey(name='q1B')), ), ])