diff --git a/graphics/comparison_n3_multi_methods.pdf b/graphics/comparison_n3_multi_methods.pdf index b8f9119..1ff67d9 100644 Binary files a/graphics/comparison_n3_multi_methods.pdf and b/graphics/comparison_n3_multi_methods.pdf differ diff --git a/graphics/raw_data_n3_original_largest_orbits_all_suborbits_semi_restricted_suborbits.npy b/graphics/raw_data_n3_original_largest_orbits_all_suborbits_semi_restricted_suborbits.npy index 2efb0a4..4687344 100644 Binary files a/graphics/raw_data_n3_original_largest_orbits_all_suborbits_semi_restricted_suborbits.npy and b/graphics/raw_data_n3_original_largest_orbits_all_suborbits_semi_restricted_suborbits.npy differ diff --git a/graphics/timing_comparison_n3.pdf b/graphics/timing_comparison_n3.pdf index d6d7b42..14e2429 100644 Binary files a/graphics/timing_comparison_n3.pdf and b/graphics/timing_comparison_n3.pdf differ diff --git a/plotting/comparison_cnot_costs.py b/plotting/comparison_cnot_costs.py index 3f38c7d..e440120 100644 --- a/plotting/comparison_cnot_costs.py +++ b/plotting/comparison_cnot_costs.py @@ -28,10 +28,10 @@ def load_module(alias_name, file_path): # CONFIGURATION: Choose which mixers to run # Set to True to run that mixer, False to skip it -RUN_ORIGINAL_LXMIXER = False +RUN_ORIGINAL_LXMIXER = True RUN_LXMIXER_LARGEST_ORBIT = True -RUN_LXMIXER_ALL_SUBORBIT = False -RUN_LXMIXER_SEMI_RESTRICTED_SUBORBIT = False +RUN_LXMIXER_ALL_SUBORBIT = True +RUN_LXMIXER_SEMI_RESTRICTED_SUBORBIT = True # Helper variable for backward compatibility RUN_LXMIXER = RUN_LXMIXER_LARGEST_ORBIT or RUN_LXMIXER_ALL_SUBORBIT or RUN_LXMIXER_SEMI_RESTRICTED_SUBORBIT @@ -798,7 +798,7 @@ def main(n, num_samples=100): plt.savefig(graphics_dir / f"Original_LXMixer_only_n{n}.pdf") else: method_str = "_".join(lxmixer_methods) - plt.savefig(graphics_dir / f"LXMixer_{method_str}_n{n}.pdf") + plt.savefig(graphics_dir / f"LXMixer_{method_str}_n{n}.pdf") plt.clf() # Create separate timing plot diff --git a/utils.py b/utils.py index 52306c7..8bf44db 100644 --- a/utils.py +++ b/utils.py @@ -192,52 +192,44 @@ def find_best_cost(Xs, Zs_operators): cost = ncnot(X_combos | Z) # We use the bitwise OR to find which qubits are acted upon by the Xs and Zs together and pass it to calculate the cost total_cost += cost #add up the cost of all Zs for that combination of Xs - all_costs[used_Xs] = total_cost #Here we store which Xs were usied and the total cost of using them with the Zs + #NB I CHANGED COMBOS TO THEIR XOR + all_costs[X_combos] = total_cost #Here we store which Xs were usied and the total cost of using them with the Zs - # Find the best combination of Xs that minimizes the cost - best_Xs = [] # List of tuples that has the combinations of the original Xs that generate the orbit, f.ex. [(2,), (8,), (2, 6)] (here 2, 8, and 6 are the original Xs) - best_cost = 0 # Total cost of the best combination of Xs - covered = set() - required = set(Xs) - maybe_later = [] - - while len(best_Xs) < n: - # We start by selecting the lowest cost from all_costs as we want to minimize the cost - lowest_cost = min(all_costs.values()) - keys = [k for k, v in all_costs.items() if v == lowest_cost] + # Brute force way + # Sorting the keys from lowest to highest cost + brute_all_keys = sorted(all_costs.keys(), key=lambda k: all_costs[k]) + brute_cost = float('inf') + brute_Xs = [] + + # Finding all combinations of n Xs (to generate an orbit of size 2^n) and checking which combination has the lowest cost and is linearly independent + for combo in combinations(brute_all_keys, n): + total_cost = sum(all_costs[key] for key in combo) + + # Disregard the combination if its cost is already higher than the best found cost + if total_cost >= brute_cost: + continue + + # Check if the combination is linearly independent and therefore valid + if not is_independent(combo): + continue - # iterate through the keys with the lowest cost - for key in keys: - # Checks that either the key adds to the subset or that it is already covered (i.e. that we are actually creating an orbit) - if (not set(key).issubset(covered)) or (required == covered): # If key has *any* uncovered elements - # Checks that if it is already covered, we use the lowest cost from maybe_later - if required == covered: - new_key_and_cost = maybe_later.pop(0) if maybe_later else [key, lowest_cost] - best_Xs.append(new_key_and_cost[0]) - best_cost += new_key_and_cost[1] - - # if the required set is not covered, we add the key to the best_Xs and update the covered set - else: - covered.update(key) - best_Xs.append(key) - best_cost += lowest_cost - - # If we have enough Xs to cover the orbit, we break the loop - if len(best_Xs) == n: - break - # We delete the key from all_costs as we have used it - del all_costs[key] + else: + brute_cost = total_cost + brute_Xs = combo - # If the key does not add to the subset, we store it in maybe_later for later use (if we get a covered set and need to add more Xs) - else: - # We delete the key from all_costs as it does not add to the subset - del all_costs[key] - maybe_later.append([key, lowest_cost]) - - # The best_Xs are reduced by applying XOR to the tuples to get the string for the combination of the original Xs - best_Xs_reduced = [reduce(operator.xor, x) for x in best_Xs] - - return best_Xs_reduced, best_cost + return brute_Xs, brute_cost #best_Xs_reduced, best_cost + +def is_independent(combo): + """Takes in a list of x-operators and checks if they are linearly independent""" + n = len(combo) + # Check all non-empty subsets of the combo to see that they are linearly independent + for r in range(1, n + 1): + for subset in combinations(combo, r): + hat = reduce(operator.xor, subset) + if hat == 0: + # Found a dependent subset + return False + return True if __name__ == '__main__': results = find_best_cost([0b0010, 0b0110, 0b1000], [(1, 0b0010), (1, 0b0110), (1, 0b1000), (1, 0b1010), (1, 0b1100), (1, 0b1110)])