From eebce21fb073d346bc30d80b91035dbe45f5178f Mon Sep 17 00:00:00 2001 From: AAA Date: Fri, 15 Aug 2025 19:54:03 -0400 Subject: [PATCH 1/4] save & load. working decoders --- graphqec/lab/threshold/threshold_lab.py | 70 ++++++++++++------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/graphqec/lab/threshold/threshold_lab.py b/graphqec/lab/threshold/threshold_lab.py index 242c009..b45f5f1 100644 --- a/graphqec/lab/threshold/threshold_lab.py +++ b/graphqec/lab/threshold/threshold_lab.py @@ -16,21 +16,21 @@ import sinter import matplotlib.pyplot as plt -from stimbposd import SinterDecoder_BPOSD -from beliefmatching import BeliefMatchingSinterDecoder +from stimbposd import sinter_decoders as bposd_sinter_decoder from mwpf import SinterMWPFDecoder +from beliefmatching import BeliefMatchingSinterDecoder from graphqec.codes.base_code import BaseCode __all__ = ["ThresholdLAB"] -__available_decoders__ = { - "pymatching": None, - "fusion_blossom": None, - "bposd": SinterDecoder_BPOSD, - "mwpf": SinterMWPFDecoder, - "beliefmatching": BeliefMatchingSinterDecoder, -} +__available_decoders__ = [ + "pymatching", + "fusion_blossom", + "bposd", + "mwpf", + "beliefmatching", +] class ThresholdLAB: @@ -70,7 +70,7 @@ def __init__( self._code = code self._error_rates = error_rates - if decoder in __available_decoders__.keys(): + if decoder in __available_decoders__: self._decoder = decoder else: ValueError("This decoder is not available.") @@ -152,8 +152,9 @@ def collect_stats( num_workers: int | None = None, max_shots: int = 10**4, max_errors: int = 1000, - decoder_params: dict[str, any] | None = None, logic_check: str = "Z", + save_resume_filepath: str | None = None, + existing_data_filepaths: list[str] = [], ) -> None: r""" Collect sampling statistics over ranges of distance and errors. @@ -162,35 +163,32 @@ def collect_stats( :param max_shots: Maximum number of shots. :param max_errors: Maximum tolerated errors. :param decoder_params: The optional decoder parameters. + :param logic_check: The logic check type. + :param save_resume_filepath: The path to the file where the statistics will be saved. + :param existing_data_filepaths: The paths to existing data files to resume from. """ if num_workers is None: num_workers = multiprocessing.cpu_count() - 1 - if __available_decoders__[self.decoder] is not None: - - # if decoder_params is None: - # custom_decoder = {self.decoder: __available_decoders__[self.decoder]()} - # else: - # custom_decoder = { - # self.decoder: __available_decoders__[self.decoder](**decoder_params) - # } - - self._samples = sinter.collect( - num_workers=num_workers, - max_shots=max_shots, - max_errors=max_errors, - tasks=self.generate_sinter_tasks(logic_check=logic_check), - decoders=[self.decoder], - custom_decoders=decoder_params, - ) - else: - self._samples = sinter.collect( - num_workers=num_workers, - max_shots=max_shots, - max_errors=max_errors, - tasks=self.generate_sinter_tasks(logic_check=logic_check), - decoders=[self.decoder], - ) + if self.decoder in ["pymatching", "fusion_blossom"]: + decoder_params = None + elif self.decoder == "bposd": + decoder_params = bposd_sinter_decoder() + elif self.decoder == "mwpf": + decoder_params = {"mwpf": SinterMWPFDecoder(cluster_node_limit=50)} + elif self.decoder == "beliefmatching": + decoder_params = {"beliefmatching": BeliefMatchingSinterDecoder()} + + self._samples = sinter.collect( + num_workers=num_workers, + max_shots=max_shots, + max_errors=max_errors, + tasks=self.generate_sinter_tasks(logic_check=logic_check), + decoders=[self.decoder], + custom_decoders=decoder_params, + save_resume_filepath=save_resume_filepath, + existing_data_filepaths=existing_data_filepaths, + ) def plot_stats( self, From 4130c9a81e483902ab06e268afbcf14436370f48 Mon Sep 17 00:00:00 2001 From: AAA Date: Sat, 16 Aug 2025 20:14:02 -0400 Subject: [PATCH 2/4] fix plot --- README.md | 2 ++ examples/rot_vs_unrot_surface_code.py | 47 +++++++++++++++++++------- graphqec/codes/rotated_surface_code.py | 3 ++ 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 053c9a4..cc60974 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ **Graph QEC** is a Python package under development that provides tools for implementing **Quantum Error Correction Codes (QECC)** by constructing their **Tanner Graphs**, automatically compiling them into **[Stim](https://github.com/quantumlib/Stim)** circuits, and computing error correction thresholds. This package allows researchers and developers to explore quantum error correction techniques, simulate quantum codes under gate error models, and analyze their performance through error thresholds. +Graph-QEC is still being prototyped and developed. It is not stable nor polished. + ## Why Graph QEC? - **Tanner Graph Representation**: Visualize and analyze quantum error correction codes through Tanner graphs, a graphical representation that simplifies the understanding of code structure and error syndromes. diff --git a/examples/rot_vs_unrot_surface_code.py b/examples/rot_vs_unrot_surface_code.py index 01ec3ef..e1f6ad8 100644 --- a/examples/rot_vs_unrot_surface_code.py +++ b/examples/rot_vs_unrot_surface_code.py @@ -22,11 +22,15 @@ def main( - configs: dict[str, int | float], + configs_rot: dict[str, int | float], + configs_unrot: dict[str, int | float], errors: np.ndarray, max_shots: int, max_errors: int, logic_check: str, + save_path_rot: str = None, + save_path_unrot: str = None, + existing_files: list[str] = [], ) -> None: r""" Compare the performance of Rotated and Unrotated Surface Codes. @@ -41,24 +45,30 @@ def main( # Get stats for the Rotated Surface Code with Z logic rot_th_z = ThresholdLAB( - configurations=configs, + configurations=configs_rot, code=RotatedSurfaceCode, error_rates=errors, decoder="pymatching", ) rot_th_z.collect_stats( - max_shots=max_shots, max_errors=max_errors, logic_check=logic_check + max_shots=max_shots, + max_errors=max_errors, + logic_check=logic_check, + save_resume_filepath=save_path_rot, ) # Get stats for the Unrotated Surface code with Z logic unrot_th_z = ThresholdLAB( - configurations=configs, + configurations=configs_unrot, code=UnrotatedSurfaceCode, error_rates=errors, decoder="pymatching", ) unrot_th_z.collect_stats( - max_shots=max_shots, max_errors=max_errors, logic_check=logic_check + max_shots=max_shots, + max_errors=max_errors, + logic_check=logic_check, + save_resume_filepath=save_path_unrot, ) # Find the range of values for the fit @@ -173,7 +183,7 @@ def main( for m, label in zip(["^", "o"], ["Unrotated", "Rotated"]) ] - height_per_entry = 0.055 + height_per_entry = 0.2 extra_for_title = 0.1 n_lines = len(marker_handles) y_marker_legend = 1.0 - (n_lines * height_per_entry + extra_for_title) @@ -190,7 +200,7 @@ def main( ax.set_xlim(min_n - 2, max_n + 2) ax.set_title("") ax.set_xlabel(r"$\sqrt{\mathrm{Number\ of\ Physical\ Qubits}}$") - ax.set_ylabel("Logical Error Rate per Round") + ax.set_ylabel("Logical Error Rate per d rounds") ax.grid(which="major", zorder=0) ax.grid(which="minor", zorder=0) plt.tight_layout() @@ -219,10 +229,11 @@ def fit( n = np.sqrt(stats.json_metadata["n"]) per_shot = stats.errors / stats.shots per_round = sinter.shot_error_rate_to_piece_error_rate( - per_shot, pieces=stats.json_metadata["r"] + per_shot, pieces=stats.json_metadata["r"] * stats.json_metadata["d"] ) xs.append(n) ys.append(per_round) + # print(f"x={xs[-1]} y={ys[-1]} for {stats.json_metadata}") log_ys.append(np.log(per_round)) fit = scipy.stats.linregress(xs, log_ys) return fit, xs, ys @@ -231,14 +242,26 @@ def fit( if __name__ == "__main__": max_shots = 1_000_000 - max_errors = 1000 + max_errors = 10000 logic_check = "Z" - configs = [{"distance": d} for d in [9, 11, 13, 17, 20, 23]] - errors = np.linspace(0.001, 0.01, 10) + # configs = [{"distance": d} for d in [9, 11, 13, 17, 20, 23]] + # errors = np.linspace(0.001, 0.01, 10) + configs_rot = [{"distance": d} for d in [5, 7, 11, 13, 17, 23]] + configs_unrot = [{"distance": d} for d in [5, 7, 11, 13, 17, 23]] + errors = np.linspace(0.005, 0.01, 5) + save_path_rot = "rot_vs_unrot_surface_code_rot.csv" + save_path_unrot = "rot_vs_unrot_surface_code_unrot.csv" + + # To add if any existing files. Cannot be the same as save_path + existing_files = [] main( - configs=configs, + configs_rot=configs_rot, + configs_unrot=configs_unrot, errors=errors, max_shots=max_shots, max_errors=max_errors, logic_check=logic_check, + save_path_rot=save_path_rot, + save_path_unrot=save_path_unrot, + existing_files=existing_files, ) diff --git a/graphqec/codes/rotated_surface_code.py b/graphqec/codes/rotated_surface_code.py index 4f10efd..2043b51 100644 --- a/graphqec/codes/rotated_surface_code.py +++ b/graphqec/codes/rotated_surface_code.py @@ -32,6 +32,9 @@ def __init__( Initialize the Rotated Surface Code instance. """ + if not distance % 2: + raise ValueError("Distance must be odd.") + self._distance = distance self._num_data_qubits = self.distance**2 self._num_logical_qubits = 1 From b9c085dce2f4de6abb56857b50ab997a710aafb7 Mon Sep 17 00:00:00 2001 From: AAA Date: Sat, 16 Aug 2025 21:36:26 -0400 Subject: [PATCH 3/4] fix round type --- graphqec/lab/threshold/threshold_lab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphqec/lab/threshold/threshold_lab.py b/graphqec/lab/threshold/threshold_lab.py index cfb5546..2aa36e3 100644 --- a/graphqec/lab/threshold/threshold_lab.py +++ b/graphqec/lab/threshold/threshold_lab.py @@ -146,7 +146,7 @@ def generate_sinter_tasks(self, logic_check: str = "Z") -> sinter.TaskGenerator: "n": int(code.num_physical_qubits), "k": int(code.num_logical_qubits), "d": int(code.distance), - "r": num_rounds, + "r": int(num_rounds), } yield sinter.Task(circuit=code.memory_circuit, json_metadata=metadata) From 056fd429e22ca201ee1c8392252c0dd7034a0ea4 Mon Sep 17 00:00:00 2001 From: AAA Date: Sat, 23 Aug 2025 08:44:04 -0400 Subject: [PATCH 4/4] fix check family --- .gitignore | 1 + examples/rot_vs_unrot_surface_code.py | 19 ++++++++++--------- graphqec/lab/threshold/threshold_lab.py | 4 ++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 123aa09..bddc9d2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .DS_Store workspace.ipynb notebooks/logic_ops.ipynb +data/* # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/examples/rot_vs_unrot_surface_code.py b/examples/rot_vs_unrot_surface_code.py index e1f6ad8..604dd4c 100644 --- a/examples/rot_vs_unrot_surface_code.py +++ b/examples/rot_vs_unrot_surface_code.py @@ -30,7 +30,8 @@ def main( logic_check: str, save_path_rot: str = None, save_path_unrot: str = None, - existing_files: list[str] = [], + existing_files_rot: list[str] = [], + existing_files_unrot: list[str] = [], ) -> None: r""" Compare the performance of Rotated and Unrotated Surface Codes. @@ -55,6 +56,7 @@ def main( max_errors=max_errors, logic_check=logic_check, save_resume_filepath=save_path_rot, + existing_data_filepaths=existing_files_rot, ) # Get stats for the Unrotated Surface code with Z logic @@ -69,6 +71,7 @@ def main( max_errors=max_errors, logic_check=logic_check, save_resume_filepath=save_path_unrot, + existing_data_filepaths=existing_files_unrot, ) # Find the range of values for the fit @@ -242,18 +245,15 @@ def fit( if __name__ == "__main__": max_shots = 1_000_000 - max_errors = 10000 + max_errors = 1000 logic_check = "Z" # configs = [{"distance": d} for d in [9, 11, 13, 17, 20, 23]] # errors = np.linspace(0.001, 0.01, 10) - configs_rot = [{"distance": d} for d in [5, 7, 11, 13, 17, 23]] - configs_unrot = [{"distance": d} for d in [5, 7, 11, 13, 17, 23]] - errors = np.linspace(0.005, 0.01, 5) + configs_rot = [{"distance": d} for d in [7, 11, 13, 17]] + configs_unrot = [{"distance": d} for d in [7, 11, 13, 17]] + errors = np.linspace(0.001, 0.008, 5) save_path_rot = "rot_vs_unrot_surface_code_rot.csv" save_path_unrot = "rot_vs_unrot_surface_code_unrot.csv" - - # To add if any existing files. Cannot be the same as save_path - existing_files = [] main( configs_rot=configs_rot, configs_unrot=configs_unrot, @@ -263,5 +263,6 @@ def fit( logic_check=logic_check, save_path_rot=save_path_rot, save_path_unrot=save_path_unrot, - existing_files=existing_files, + existing_files_rot=[], + existing_files_unrot=[], ) diff --git a/graphqec/lab/threshold/threshold_lab.py b/graphqec/lab/threshold/threshold_lab.py index 2aa36e3..d1c5716 100644 --- a/graphqec/lab/threshold/threshold_lab.py +++ b/graphqec/lab/threshold/threshold_lab.py @@ -261,5 +261,5 @@ def check_is_family(samples: list[sinter.TaskStats]) -> bool: r""" Check if the given samples belong to the same family. """ - first_name = samples[0].json_metadata["name"] - return any(sample.json_metadata["name"] != first_name for sample in samples) + lst = [sample.json_metadata["name"] for sample in samples] + return all(x == lst[0] for x in lst)