Skip to content

Commit

Permalink
Update entangled measurements grouping draft code
Browse files Browse the repository at this point in the history
Script runs now, but still buggy. Main bug identified so far is compatibility check between one-qubit Pauli measurements and Bell measurements
  • Loading branch information
chmwzc committed Dec 24, 2024
1 parent c82ddf7 commit e4456e1
Showing 1 changed file with 137 additions and 132 deletions.
269 changes: 137 additions & 132 deletions example/grouping.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
WARNING: Draft code that doesn't work!!!!
Bug: Check of compatibility between one-qubit measurements (e.g. "Z0") with Bell measurements (e.g. "Bell01) fails;
- Should be incompatible, but currently returning compatible
Otherwise seems to be OK...?
"""

import networkx as nx
Expand All @@ -15,46 +19,6 @@
from qibochem.measurement import expectation
from qibochem.measurement.optimization import check_terms_commutativity

#
# ZC NOTE: Was trying to generate all combinations of qubit pairs. Seems like not supposed to do it this way...?
#
# def generate_entangled_measurements(measurements, entanglements=None):
# """
# Generate all possible pairs of qubits that can be measured using entangled measurements
#
# Args:
# measurements (dict): TPB measurements, e.g. {1: "X", 2: "Z", ...}
# entanglement_type: Type of entangled measurements to consider. Currently only Bell measurements implemented
#
# Returns:
# (dict): Keys are qubits, values are the possible measurements (TPB and Bell) for that qubit
# """
# if entanglements is None:
# entanglements = (("X", "X"), ("Y", "Y"), ("Z", "Z"))
#
# result = {}
# for _i, (qubit1, measurement1) in enumerate(measurements.items()):
# remaining_qubits = list(measurements.keys())
# # print(remaining_qubits)
# new_measurements = {}
# for _j, (qubit2, measurement2) in enumerate(measurements.items()):
# if _j > _i:
# if (measurement1, measurement2) in entanglements:
# # Change the measurement to a Bell measurement if possible
# new_measurements = {**new_measurements, **{qubit1: "Bell", qubit2: "Bell"}}
# remaining_qubits.remove(qubit1)
# remaining_qubits.remove(qubit2)
# # print((qubit1, measurement1), (qubit2, measurement2))
# # print(remaining_qubits)
# break
# # Add in the original TPB measurements for the remaining qubits.
# new_measurements = {**new_measurements, **{qubit:measurements[qubit] for qubit in remaining_qubits}}
# if new_measurements not in result:
# result.append(new_measurements)
# # print(result)
# # print()
# return result


def generate_entangled_measurements(measurements, entanglements=None):
"""
Expand All @@ -81,10 +45,10 @@ def generate_entangled_measurements(measurements, entanglements=None):
if _j > _i:
if (measurement1, measurement2) in entanglements:
# Change the measurement to a Bell measurement if possible
if "Bell" not in result[qubit1]:
result[qubit1].append("Bell")
if "Bell" not in result[qubit2]:
result[qubit2].append("Bell")
if f"Bell{qubit1}{qubit2}" not in result[qubit1]:
result[qubit1].append(f"Bell{qubit1}{qubit2}")
if f"Bell{qubit1}{qubit2}" not in result[qubit2]:
result[qubit2].append(f"Bell{qubit1}{qubit2}")
return result


Expand All @@ -99,6 +63,53 @@ def check_compatible(term1_measurements, term2_measurements):
return compatible


def select_measurement(possible_measurements):
"""
Select appropriate measurements from all possible measurements. Roughly corresponds to lines 6 to 17 in Algorithm 2
TODO: Add other entangled measurements beside Bell measurements
"""
# Add in single qubit Pauli measurements first
result = {
qubit: measurements[0]
for qubit, measurements in possible_measurements.items()
if len(measurements) == 1 and len(measurements[0]) == 1
}
# Add possible Bell measurements next
n_entangled_measurements = 0
to_remove = None
remaining_qubits = list(qubit for qubit in possible_measurements.keys() if qubit not in result.keys())
while remaining_qubits:
# Find the possible Bell pairs of qubits and select the first pair
# TODO: Could probably write this more nicely
bell_pair = None
for qubit in remaining_qubits:
for measurement in possible_measurements[qubit]:
if measurement.startswith("Bell"):
possible_bell_pair = (int(measurement[4]), int(measurement[5]))
if all(_q in remaining_qubits for _q in possible_bell_pair):
bell_pair = possible_bell_pair
break
if bell_pair is not None:
break
# print("Bell pair:", bell_pair)

if bell_pair is None:
print("Cannot find any pair of qubits to use entangled measurements!")
# Set all qubits to be single Pauli measurements
result = {
**result,
**{
qubit: possible_measurements[qubit][0] # Should be sorted to give single qubit Pauli terms first
for qubit in remaining_qubits
},
}
break
for qubit in bell_pair:
result[qubit] = f"Bell{bell_pair[0]}{bell_pair[1]}"
remaining_qubits.remove(qubit)
return result


def main():
# Global variables:
bell_measurements = (("X", "X"), ("Y", "Y"), ("Z", "Z"))
Expand Down Expand Up @@ -171,107 +182,101 @@ def main():
remaining_nodes = list(all_nodes) # Make a copy of all_nodes to track which have been merged
measurement_groups = [] # Also need to record what measurements to use for each group

for _i, node1 in enumerate(all_nodes):
if node1 in remaining_nodes:
# Base case: grouping is empty, just add the first node in directly
if not grouping:
remaining_nodes.remove(node1)
grouping.append([node1])
measurement_groups.append(tpb_measurements[node1])
# continue
for _j, node2 in enumerate(all_nodes):
if _j > _i and node2 in remaining_nodes:
# Should have something in grouping by now
group_index = None # Flag to see which group to add node2 to
for _k, group in enumerate(grouping):

# Algorithm 2 in the reference paper. Not 100% sure is correct!
# Check whether current pair of Pauli strings is compatible
if check_compatible(all_measurements[node1], all_measurements[node2]):
print("Node1:", node1)
print("Node2:", node2)

while remaining_nodes:
for _i, node1 in enumerate(all_nodes):
if node1 in remaining_nodes:
print("Node1:", node1)
# Base case: grouping is empty, just add the first node in directly
if not grouping:
remaining_nodes.remove(node1)
grouping.append([node1])
measurement_groups.append(tpb_measurements[node1])
# continue
for _j, node2 in enumerate(all_nodes):
if _j > _i and node2 in remaining_nodes:
print("Node2:", node2)
# Should have something in grouping by now
group_index = None # Flag to see which group to add node2 to
for _k, group in enumerate(grouping):
print(f"Group {_k}: {group}")
# Get the measurements for the current group
measurements = measurement_groups[_k]
print(measurements)
# Generate permutations of overlapping qubit positions with different terms and check if
# any of the entangled measurements can be applied to the position
overlapping_qubits = [
qubit
for qubit, measurement in measurements.items()
if all_measurements[node2].get(qubit) is not None
and measurement in all_measurements[node2][qubit]
]
# Try to replace single Pauli measurements with entangled measurements
new_measurements = {}
count = 0
while overlapping_qubits:
to_remove = []
print(f"Overlapping qubits: {overlapping_qubits}")
n_qubits = len(overlapping_qubits)
# Cannot have an odd number of overlapping qubits
if n_qubits % 2 == 1:
new_measurements = {}
break
for _l in range(n_qubits):
qubit1 = overlapping_qubits[_l]
for _m in range(n_qubits):
qubit2 = overlapping_qubits[_m]
if _m > _l and not to_remove:
# Check to see if (qubit1, qubit2) can be measured using Bell measurements
if (
any(
(measurements[qubit1], m_type) in bell_measurements
for m_type in all_measurements[node2][qubit2]
)
or (all_measurements[node2][qubit1], all_measurements[node2][qubit1])
in bell_measurements
):
new_measurements = {
**new_measurements,
**{qubit1: "Bell", qubit2: "Bell"},
}
to_remove.append(qubit1)
to_remove.append(qubit2)
break
for qubit in to_remove:
overlapping_qubits.remove(qubit)
count += 1
if count > 100:
break
# Any successful change in the measurements?
if new_measurements:
group_index = _k
# Update measurements dictionary
measurements = {**measurements, **new_measurements}
# Add measurements for the remaining qubits (in node2 but not in node1)
measurements = {
current_measurements = measurement_groups[_k]
# Add entangled measurements in as well
measurements = generate_entangled_measurements(current_measurements)
print(f"Current possible measurements: {measurements}")

# Algorithm 2 in the reference paper. Not 100% sure is correct!
# Check whether current pair of Pauli strings is compatible with current set of measurements
if check_compatible(measurements, all_measurements[node2]):
print(f"node1 ({measurements}) and node2 ({all_measurements[node2]}) compatible!")
# Generate permutations of overlapping qubit positions with different terms and check if
# any of the entangled measurements can be applied to the position (???)
possible_new_measurements = {
qubit: sorted(possible_measurements, key=len)
for qubit in set(measurements.keys()) & set(all_measurements[node2].keys())
if (
possible_measurements := set(measurements[qubit])
& set(all_measurements[node2][qubit])
)
}
# Add on measurements for qubits in current set of measurements, but not in the new node considered
possible_new_measurements = {
**measurements,
**possible_new_measurements,
}
print(f"Possible new measurements: {possible_new_measurements} (Overlap with current)")
if not possible_new_measurements:
possible_new_measurements = {**measurements, **all_measurements[node2]}
print(
f"Possible new measurements: {possible_new_measurements} (No overlap with current)"
)
# Add on measurements for qubits not in current set of measurements, but in the new node considered
# TODO: Probably can merge with the previous if condition...?
possible_new_measurements = {**all_measurements[node2], **possible_new_measurements}

new_measurements = select_measurement(possible_new_measurements)
print(f"New measurements: {new_measurements}")
# Add measurements for the remaining qubits (in node2 but not in current set of measurements)
new_measurements = {
**new_measurements,
**{
qubit: measurement
for qubit, measurement in tpb_measurements[node2].items()
if qubit not in measurements.keys()
if qubit not in new_measurements.keys()
},
}

if group_index is None:
grouping.append([node2])
measurement_groups.append(tpb_measurements[node2])
else:
grouping[group_index].append(node2)
measurement_groups[group_index] = measurements
remaining_nodes.remove(node2)

print("Remaining nodes:")
print(remaining_nodes)

print("\nFinal grouping")
print(grouping)
group_index = _k
break
else:
print(f"node1 ({node1}) and node2 ({node2}) not compatible!")

if group_index is None:
print("No current group compatible. Creating new measurement group")
grouping.append([node2])
measurement_groups.append(tpb_measurements[node2])
else:
print(f"Adding {new_measurements} to group {group_index}")
grouping[group_index].append(node2)
measurement_groups[group_index] = new_measurements
remaining_nodes.remove(node2)

print()

print("Remaining nodes:")
print(remaining_nodes)
print()

# Check to ensure all nodes were placed in a group
grouped_terms = {term for group in grouping for term in group}
assert grouped_terms == set(all_nodes)

print("\nFinal grouping and measurements")
for group, measurement in zip(grouping, measurement_groups):
print(group)
print(measurement)
print()


if __name__ == "__main__":
main()

0 comments on commit e4456e1

Please sign in to comment.