From 0827505f17aef3f37a8d1879aba56e41fae9e9a5 Mon Sep 17 00:00:00 2001 From: eshant742 Date: Thu, 13 Feb 2025 19:16:35 +0530 Subject: [PATCH 1/3] noise model done --- graphqec/codes/base_code.py | 289 +++++++++++++----------- graphqec/lab/threshold/threshold_lab.py | 86 +++---- graphqec/noise_model.py | 35 +++ tests/codes/test_base_code.py | 41 ++++ tests/codes/test_noise_model.py | 29 +++ 5 files changed, 310 insertions(+), 170 deletions(-) create mode 100644 graphqec/noise_model.py create mode 100644 tests/codes/test_base_code.py create mode 100644 tests/codes/test_noise_model.py diff --git a/graphqec/codes/base_code.py b/graphqec/codes/base_code.py index 4bf8cbb..aea21c2 100644 --- a/graphqec/codes/base_code.py +++ b/graphqec/codes/base_code.py @@ -1,3 +1,5 @@ +# graphqec/codes/base_code.py + # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -21,6 +23,9 @@ from graphqec.measurement import Measurement from graphqec.stab import X_check, Z_check +# NEW: Import your NoiseModel and a default DepolarizingNoiseModel +from graphqec.noise_models import NoiseModel, DepolarizingNoiseModel + __all__ = ["BaseCode"] @@ -30,7 +35,8 @@ class BaseCode(ABC): """ __slots__ = ( - "_name" "_distance", + "_name", + "_distance", "_memory_circuit", "_depolarize1_rate", "_depolarize2_rate", @@ -38,6 +44,7 @@ class BaseCode(ABC): "_graph", "_checks", "_logic_check", + "_noise_model", # NEW SLOT ) def __init__( @@ -45,6 +52,7 @@ def __init__( distance: int = 3, depolarize1_rate: float = 0, depolarize2_rate: float = 0, + noise_model: NoiseModel = None, # NEW PARAM ) -> None: r""" Initialization of the Base Code class. @@ -52,17 +60,23 @@ def __init__( :param distance: Distance of the code. :param depolarize1_rate: Single qubit depolarization rate. :param depolarize2_rate: Two qubit depolarization rate. + :param noise_model: An optional NoiseModel instance (defaults to DepolarizingNoiseModel). """ + self._name = self.__class__.__name__ # Or set this however you'd like self._distance = distance self._depolarize1_rate = depolarize1_rate self._depolarize2_rate = depolarize2_rate + + # If no noise model is given, default to DepolarizingNoiseModel + self._noise_model = noise_model if noise_model else DepolarizingNoiseModel() + self._memory_circuit: Circuit self._measurement = Measurement() - self._checks: list[str] - self._logic_check: list[str] - + self._checks: list[str] = [] + self._logic_check: list[str] = [] self._graph = nx.Graph() + self.build_graph() @property @@ -89,14 +103,14 @@ def memory_circuit(self) -> Circuit: @property def depolarize1_rate(self) -> float: r""" - The depolarization rate for single qubit gate. + The depolarization rate for single-qubit gates. """ return self._depolarize1_rate @property def depolarize2_rate(self) -> float: r""" - The depolarization rate for two-qubit gate. + The depolarization rate for two-qubit gates. """ return self._depolarize2_rate @@ -110,28 +124,28 @@ def measurement(self) -> Measurement: @property def register_count(self) -> int: r""" - The number of outcome collected. + The number of outcomes collected. """ return self.measurement.register_count @property - def graph(self) -> int: + def graph(self) -> nx.Graph: r""" - The graph representing qubits network + The graph representing the qubits' network. """ return self._graph @property - def checks(self) -> int: + def checks(self) -> list[str]: r""" - The different checks in the QEC + The different checks (e.g., 'Z-check', 'X-check') in the QEC. """ return self._checks @property - def logic_check(self) -> int: + def logic_check(self) -> list[str]: r""" - Return logic check. + Return logic check qubits. """ return self._logic_check @@ -147,8 +161,7 @@ def build_memory_circuit(self, number_of_rounds: int) -> None: :param number_of_rounds: The number of rounds in the memory. """ - - all_qubits = [q for q in self.graph.nodes()] + all_qubits = list(self.graph.nodes()) data_qubits = [ node for node, data in self.graph.nodes(data=True) @@ -163,40 +176,51 @@ def build_memory_circuit(self, number_of_rounds: int) -> None: if data.get("type") == check ] - temp = [item for item in check_qubits.values()] - all_check_qubits = [item for sublist in temp for item in sublist] + all_check_qubits = [q for sublist in check_qubits.values() for q in sublist] # Initialization self._memory_circuit = Circuit() - self._memory_circuit.append("R", all_qubits) - self._memory_circuit.append("DEPOLARIZE1", all_qubits, self.depolarize1_rate) + + # Apply single-qubit noise to all qubits + self._noise_model.apply_noise( + circuit=self._memory_circuit, + noise_type="single", + qubits=all_qubits, + rate=self.depolarize1_rate, + ) self.append_stab_circuit( round=0, data_qubits=data_qubits, check_qubits=check_qubits ) - for qz in check_qubits["Z-check"]: - rec = self.get_target_rec(qubit=qz, round=0) - self._memory_circuit.append("DETECTOR", [target_rec(rec)]) + # Add DETECTOR instructions for the first round + if "Z-check" in check_qubits: + for qz in check_qubits["Z-check"]: + rec = self.get_target_rec(qubit=qz, round=0) + self._memory_circuit.append("DETECTOR", [target_rec(rec)]) # Body rounds - for round in range(1, number_of_rounds): - + for r in range(1, number_of_rounds): self.append_stab_circuit( - round=round, data_qubits=data_qubits, check_qubits=check_qubits + round=r, data_qubits=data_qubits, check_qubits=check_qubits ) for q in all_check_qubits: - past_rec = self.get_target_rec(qubit=q, round=round - 1) - current_rec = self.get_target_rec(qubit=q, round=round) + past_rec = self.get_target_rec(qubit=q, round=r - 1) + current_rec = self.get_target_rec(qubit=q, round=r) self._memory_circuit.append( "DETECTOR", [target_rec(past_rec), target_rec(current_rec)], ) # Finalization - self._memory_circuit.append("DEPOLARIZE1", data_qubits, self.depolarize1_rate) + self._noise_model.apply_noise( + circuit=self._memory_circuit, + noise_type="single", + qubits=data_qubits, + rate=self.depolarize1_rate, + ) self._memory_circuit.append("M", data_qubits) for i, q in enumerate(data_qubits): @@ -205,98 +229,122 @@ def build_memory_circuit(self, number_of_rounds: int) -> None: ) # Syndrome extraction grouping data qubits - for qz in check_qubits["Z-check"]: - - qz_adjacent_data_qubits = self.graph.neighbors(qz) - - recs = [ - self.get_target_rec(qubit=qd, round=number_of_rounds) - for qd in qz_adjacent_data_qubits - ] - recs += [self.get_target_rec(qubit=qz, round=number_of_rounds - 1)] - - self._memory_circuit.append("DETECTOR", [target_rec(r) for r in recs]) + if "Z-check" in check_qubits: + for qz in check_qubits["Z-check"]: + qz_adjacent_data_qubits = list(self.graph.neighbors(qz)) + recs = [ + self.get_target_rec(qubit=qd, round=number_of_rounds) + for qd in qz_adjacent_data_qubits + ] + recs += [self.get_target_rec(qubit=qz, round=number_of_rounds - 1)] + self._memory_circuit.append("DETECTOR", [target_rec(r) for r in recs]) # Adding the comparison with the expected state recs = [ self.get_target_rec(qubit=q, round=number_of_rounds) for q in self.logic_check ] - recs_str = " ".join(f"rec[{rec}]" for rec in recs) - self._memory_circuit.append_from_stim_program_text( - f"OBSERVABLE_INCLUDE(0) {recs_str}" - ) + if recs: + recs_str = " ".join(f"rec[{rec}]" for rec in recs if rec is not None) + self._memory_circuit.append_from_stim_program_text( + f"OBSERVABLE_INCLUDE(0) {recs_str}" + ) def append_stab_circuit( self, round: int, data_qubits: list[int], check_qubits: dict[str, list[int]] ) -> None: r""" - Append the stabilizer circuit. + Append the stabilizer circuit for one round. """ + all_check_qubits = [q for sublist in check_qubits.values() for q in sublist] - temp = [item for item in check_qubits.values()] - all_check_qubits = [item for sublist in temp for item in sublist] - + # Apply single-qubit noise to check qubits at the start of each round > 0 if round > 0: - self._memory_circuit.append( - "DEPOLARIZE1", all_check_qubits, self.depolarize1_rate + self._noise_model.apply_noise( + circuit=self._memory_circuit, + noise_type="single", + qubits=all_check_qubits, + rate=self.depolarize1_rate, ) - if "X-check" in self.checks: - self._memory_circuit.append("H", [q for q in check_qubits["X-check"]]) - self._memory_circuit.append( - "DEPOLARIZE1", - [q for q in check_qubits["X-check"]], - self.depolarize1_rate, + # If there are X-check qubits, apply H and noise + if "X-check" in check_qubits: + x_checks = check_qubits["X-check"] + self._memory_circuit.append("H", x_checks) + self._noise_model.apply_noise( + circuit=self._memory_circuit, + noise_type="single", + qubits=x_checks, + rate=self.depolarize1_rate, ) - # A flag to tell us if a data qubit was used this round + # Track which data qubits were used in this round measured = {qd: False for qd in data_qubits} - # Perform CNOTs with specific order to avoid hook errors + # Perform CNOTs in specific weight order to avoid hook errors for order in range(1, 5): - for check in check_qubits.keys(): - for q in check_qubits[check]: - data = [ + for check_type, qubits_list in check_qubits.items(): + for check_q in qubits_list: + data_neighbors = [ neighbor - for neighbor, attrs in self.graph[q].items() + for neighbor, attrs in self.graph[check_q].items() if attrs.get("weight") == order ] - if len(data) == 1: - data = data[0] + if len(data_neighbors) == 1: + data = data_neighbors[0] self.append_stab_element( - data_qubit=data, check_qubit=q, check=check + data_qubit=data, + check_qubit=check_q, + check=check_type, ) - self._memory_circuit.append( - "DEPOLARIZE2", [data, q], self.depolarize2_rate + # Apply two-qubit noise after each CNOT + self._noise_model.apply_noise( + circuit=self._memory_circuit, + noise_type="two", + qubits=[data, check_q], + rate=self.depolarize2_rate, ) measured[data] = True - # Apply depolarization channel to account for the time not being used - not_measured = [key for key, value in measured.items() if value is False] - self._memory_circuit.append("DEPOLARIZE1", not_measured, self.depolarize1_rate) + # Apply single-qubit noise to any data qubits not used this round + not_measured = [qd for qd, used in measured.items() if not used] + self._noise_model.apply_noise( + circuit=self._memory_circuit, + noise_type="single", + qubits=not_measured, + rate=self.depolarize1_rate, + ) - if "X-check" in self.checks: - self._memory_circuit.append("H", [q for q in check_qubits["X-check"]]) - self._memory_circuit.append( - "DEPOLARIZE1", - [q for q in check_qubits["X-check"]], - self.depolarize1_rate, + # If there are X-check qubits, apply another H and noise + if "X-check" in check_qubits: + x_checks = check_qubits["X-check"] + self._memory_circuit.append("H", x_checks) + self._noise_model.apply_noise( + circuit=self._memory_circuit, + noise_type="single", + qubits=x_checks, + rate=self.depolarize1_rate, ) - self._memory_circuit.append( - "DEPOLARIZE1", [q for q in all_check_qubits], self.depolarize1_rate + # Apply single-qubit noise to all check qubits before measurement + self._noise_model.apply_noise( + circuit=self._memory_circuit, + noise_type="single", + qubits=all_check_qubits, + rate=self.depolarize1_rate, ) - self._memory_circuit.append("MR", [q for q in all_check_qubits]) + + # Measure check qubits + self._memory_circuit.append("MR", all_check_qubits) for i, q in enumerate(all_check_qubits): self.add_outcome( outcome=target_rec(-1 - i), qubit=q, round=round, type="check" ) - def append_stab_element( - self, data_qubit: any, check_qubit: any, check: str - ) -> None: - + def append_stab_element(self, data_qubit: int, check_qubit: int, check: str) -> None: + """ + Append the appropriate stabilizer operation (Z_check or X_check). + """ if check == "Z-check": Z_check( circ=self._memory_circuit, @@ -310,44 +358,30 @@ def append_stab_element( check_qubit=check_qubit, ) elif check == "Y-check": - ValueError("This check is not implemented.") + raise ValueError("Y-check is not implemented.") else: - ValueError("This check is not implemented.") + raise ValueError(f"Unknown check type '{check}'.") - def get_outcome( - self, - qubit: any, - round: int, - ) -> any: + def get_outcome(self, qubit: int, round: int) -> any: r""" Return the outcome for the qubit at the specified round or None. - - :param qubit: The qubit on which the measurement is performed. - :param round: The round during which the measurement is performed. """ return self._measurement.get_outcome(qubit=qubit, round=round) - def add_outcome( - self, outcome: any, qubit: any, round: int, type: str | None - ) -> None: + def add_outcome(self, outcome: any, qubit: int, round: int, type: str | None) -> None: r""" Add an outcome to the collection. - - :param outcome: The outcome to store. - :param qubit: The qubit on which the measurement is performed. - :param round: The round during which the measurement is performed. - :param type: The type of measurement. """ self._measurement.add_outcome( - outcome=outcome, qubit=qubit, round=round, type=type + outcome=outcome, + qubit=qubit, + round=round, + type=type, ) - def get_target_rec(self, qubit: any, round: int) -> int | None: + def get_target_rec(self, qubit: int, round: int) -> int | None: r""" Return the rec of a specific measurement. - - :param qubit: The qubit on which the measurement is performed. - :param round: The round during which the measurement is performed. """ try: return ( @@ -359,45 +393,37 @@ def get_target_rec(self, qubit: any, round: int) -> int | None: def draw_graph(self) -> None: r""" - Draw the graph. + Draw the qubit graph. """ - - # Extract qubit type for coloring + # Extract qubit types for coloring node_categories = nx.get_node_attributes(self.graph, "type") - - # Get the unique types unique_categories = sorted(set(node_categories.values())) - # Custom color palette + # Define a custom color palette custom_colors = { - "data": "#D3D3D3", # grey + "data": "#D3D3D3", # grey "Z-check": "#d62728", # red "X-check": "#1f77b4", # blue "Y-check": "#2ca02c", # green - # Add more colors if you have more types } - # Ensure that each type has a corresponding color + # Map node types to colors node_colors = [ - custom_colors.get( - node_categories[node], "#808080" - ) # default to gray if type is not in custom_colors + custom_colors.get(node_categories[node], "#808080") for node in self.graph.nodes() ] - # Define a layout + # Define layout try: pos = { - int(node[0]): (node[1]["coords"][0], node[1]["coords"][1]) - for node in self.graph.nodes(data=True) + node: (data["coords"][0], data["coords"][1]) + for node, data in self.graph.nodes(data=True) } except KeyError: pos = nx.spring_layout(self.graph) # Draw the graph plt.figure(figsize=(6, 6)) - - # Draw the graph with node numbers and colors nx.draw( self.graph, pos, @@ -407,24 +433,25 @@ def draw_graph(self) -> None: font_size=8, font_weight="bold", edge_color="gray", - width=1, # Edge width (adjust as needed) + width=1, ) - # Add edge weights as labels + # Draw edge weights edge_labels = nx.get_edge_attributes(self.graph, "weight") nx.draw_networkx_edge_labels( self.graph, pos, edge_labels=edge_labels, font_size=8, font_weight="bold" ) - # Create and display custom legend patches for each unique type + # Create legend category_legend = [ - mpatches.Patch(color=custom_colors[category], label=f"{category} qubit") - for category in unique_categories + mpatches.Patch(color=custom_colors[cat], label=f"{cat} qubit") + for cat in unique_categories ] - - # Display the graph plt.legend( - handles=category_legend, loc="upper left", bbox_to_anchor=(1, 1), title="" + handles=category_legend, + loc="upper left", + bbox_to_anchor=(1, 1), + title="Qubit Types", ) - plt.title("") + plt.title(f"{self.name} Graph") plt.show() diff --git a/graphqec/lab/threshold/threshold_lab.py b/graphqec/lab/threshold/threshold_lab.py index efda1cf..96a8c5e 100644 --- a/graphqec/lab/threshold/threshold_lab.py +++ b/graphqec/lab/threshold/threshold_lab.py @@ -16,14 +16,19 @@ import matplotlib.pyplot as plt import pymatching +# If your code parameter is actually a class (subclass of BaseCode), +# you may want to annotate it with `type[BaseCode]` instead of `BaseCode`. from graphqec.codes.base_code import BaseCode +# Import the NoiseModel interface (and possibly other models) if needed +from graphqec.noise_models import NoiseModel + __all__ = ["ThresholdLAB"] class ThresholdLAB: r""" - A class for wrapping threshold calculation + A class for wrapping threshold calculation. """ __slots__ = ( @@ -35,55 +40,45 @@ class ThresholdLAB: ) def __init__( - self, code: BaseCode, distances: list[int], error_rates: list[float] + self, + code: BaseCode, # or `type[BaseCode]` if `code` is a class, not an instance + distances: list[int], + error_rates: list[float] ) -> None: r""" - Initialization of the Base Code class. - - :param code: The code + :param code: The code or code class to use. :param distances: Distances for the code. - :param error_rates: Error rate. + :param error_rates: Error rates. """ - self._distances = distances self._code = code - self._code_name = code().name + self._code_name = code().name # Instantiating once to get the name self._error_rates = error_rates self._collected_stats = {} @property def distances(self) -> list[int]: - r""" - The distances of the code. - """ + r"""The distances of the code.""" return self._distances @property def error_rates(self) -> list[float]: - r""" - The error rates. - """ + r"""The error rates.""" return self._error_rates @property def code(self) -> BaseCode: - r""" - The code. - """ + r"""The code or code class.""" return self._code @property def collected_stats(self) -> dict: - r""" - The collected stats during sampling. - """ + r"""The collected stats during sampling.""" return self._collected_stats @property def code_name(self) -> str: - r""" - The code name. - """ + r"""The code name.""" return self._code_name @staticmethod @@ -91,17 +86,16 @@ def compute_logical_errors(code: BaseCode, num_shots: int) -> int: r""" Sample the memory circuit and return the number of errors. - :param code: The code to simulate. + :param code: The code instance to simulate. :param num_shots: The number of samples. """ - # Sample the memory circuit sampler = code.memory_circuit.compile_detector_sampler() detection_events, observable_flips = sampler.sample( num_shots, separate_observables=True ) - # Configure the decoder using the memory circuit then run the decoder + # Configure the decoder using the memory circuit, then run the decoder detector_error_model = code.memory_circuit.detector_error_model( decompose_errors=False ) @@ -118,28 +112,33 @@ def compute_logical_errors(code: BaseCode, num_shots: int) -> int: num_errors += 1 return num_errors - def collect_stats(self, num_shots: int) -> None: - r"""Collect sampling statistics over ranges of distance and errors.""" + def collect_stats(self, num_shots: int, noise_model: NoiseModel = None) -> None: + r""" + Collect sampling statistics over ranges of distance and errors. + + :param num_shots: Number of samples for each distance/error-rate pair. + :param noise_model: Optional noise model to use; if None, the code's default is used. + """ # Loop over distance range for distance in self.distances: - temp_logical_error_rate = [] - # Loop over physical errors + # Loop over physical error rates for prob_error in self.error_rates: - - # Build the circuit for the code - code = self.code( + # Instantiate the code with the chosen distance, error rates, and optional noise model + code_instance = self.code( distance=distance, depolarize1_rate=prob_error, depolarize2_rate=prob_error, + noise_model=noise_model ) - code.build_memory_circuit(number_of_rounds=distance * 3) + code_instance.build_memory_circuit(number_of_rounds=distance * 3) # Get the logical error rate num_errors_sampled = self.compute_logical_errors( - code=code, num_shots=num_shots + code=code_instance, + num_shots=num_shots ) temp_logical_error_rate.append(num_errors_sampled / num_shots) @@ -152,13 +151,21 @@ def plot_stats( y_min: float | None = None, y_max: float | None = None, ) -> None: - r"""Plot the collected data""" + r""" + Plot the collected data. + :param x_min: Optional lower bound for the x-axis. + :param x_max: Optional upper bound for the x-axis. + :param y_min: Optional lower bound for the y-axis. + :param y_max: Optional upper bound for the y-axis. + """ fig, ax = plt.subplots(1, 1) - for distance in self.collected_stats.keys(): + for distance, error_rates in self._collected_stats.items(): ax.plot( - self.error_rates, self.collected_stats[distance], label=f"d={distance}" + self.error_rates, + error_rates, + label=f"d={distance}" ) if x_min is not None and x_max is not None: @@ -168,9 +175,10 @@ def plot_stats( ax.loglog() ax.set_title(f"{self.code_name} Code Error Rates") - ax.set_xlabel("Phyical Error Rate") + ax.set_xlabel("Physical Error Rate") ax.set_ylabel("Logical Error Rate") ax.grid(which="major") ax.grid(which="minor") ax.legend() fig.set_dpi(120) + plt.show() diff --git a/graphqec/noise_model.py b/graphqec/noise_model.py new file mode 100644 index 0000000..2968501 --- /dev/null +++ b/graphqec/noise_model.py @@ -0,0 +1,35 @@ +# graphqec/noise_models.py +from abc import ABC, abstractmethod +from stim import Circuit + +class NoiseModel(ABC): + @abstractmethod + def apply_noise(self, circuit: Circuit, noise_type: str, qubits: list[int], rate: float) -> None: + """ + Apply noise to the given circuit. + + :param circuit: The Stim circuit to which noise is applied. + :param noise_type: A string indicating the noise type (e.g., "single" or "two"). + :param qubits: The list of qubit indices on which to apply noise. + :param rate: The noise rate (error probability). + """ + pass + + +class DepolarizingNoiseModel(NoiseModel): + def apply_noise(self, circuit: Circuit, noise_type: str, qubits: list[int], rate: float) -> None: + """ + Concrete implementation of depolarizing noise using Stim instructions: + - "DEPOLARIZE1" for single-qubit noise. + - "DEPOLARIZE2" for two-qubit noise. + """ + if not qubits or rate == 0: + # No qubits or zero rate => no noise instruction needed + return + + if noise_type == "single": + circuit.append("DEPOLARIZE1", qubits, rate) + elif noise_type == "two": + circuit.append("DEPOLARIZE2", qubits, rate) + else: + raise ValueError(f"Unknown noise type: {noise_type}") diff --git a/tests/codes/test_base_code.py b/tests/codes/test_base_code.py new file mode 100644 index 0000000..f879002 --- /dev/null +++ b/tests/codes/test_base_code.py @@ -0,0 +1,41 @@ +# tests/codes/test_base_code.py + +import pytest +import stim + +from graphqec.codes.base_code import BaseCode +from graphqec.noise_models import DepolarizingNoiseModel + +class MinimalTestCode(BaseCode): + def build_graph(self): + """ + Build a trivial graph with: + - 1 data qubit (node 0) + - 1 Z-check qubit (node 1) + - An edge connecting them with weight=1 + """ + self._graph.add_node(0, type="data") + self._graph.add_node(1, type="Z-check") + self._checks = ["Z-check"] + self._logic_check = [] + self._graph.add_edge(0, 1, weight=1) + +def test_base_code_noise_integration(): + """ + Verify that building the memory circuit inserts the correct noise instructions. + """ + code = MinimalTestCode( + distance=3, + depolarize1_rate=0.01, + depolarize2_rate=0.02, + noise_model=DepolarizingNoiseModel(), + ) + code.build_memory_circuit(number_of_rounds=2) + circuit = code.memory_circuit + text_diagram = circuit.to_text_diagram() + + # Check for single-qubit noise + assert "DEPOLARIZE1(0.01)" in text_diagram, "Expected single-qubit noise not found in circuit." + + # Check for two-qubit noise + assert "DEPOLARIZE2(0.02)" in text_diagram, "Expected two-qubit noise not found in circuit." diff --git a/tests/codes/test_noise_model.py b/tests/codes/test_noise_model.py new file mode 100644 index 0000000..124094c --- /dev/null +++ b/tests/codes/test_noise_model.py @@ -0,0 +1,29 @@ +# tests/codes/test_noise_model.py + +import stim +import pytest +from graphqec.noise_models import DepolarizingNoiseModel + +def test_depolarizing_noise_single_qubit(): + """Check single-qubit depolarizing noise.""" + circuit = stim.Circuit() + noise_model = DepolarizingNoiseModel() + + noise_model.apply_noise(circuit, "single", [0], 0.01) + text_diagram = circuit.to_text_diagram() + + assert "DEPOLARIZE1(0.01) 0" in text_diagram, ( + "Expected single-qubit depolarizing noise instruction missing." + ) + +def test_depolarizing_noise_two_qubit(): + """Check two-qubit depolarizing noise.""" + circuit = stim.Circuit() + noise_model = DepolarizingNoiseModel() + + noise_model.apply_noise(circuit, "two", [0, 1], 0.02) + text_diagram = circuit.to_text_diagram() + + assert "DEPOLARIZE2(0.02) 0 1" in text_diagram, ( + "Expected two-qubit depolarizing noise instruction missing." + ) From af4cb56739fc0415d4b15ac00dd1efc15e19b241 Mon Sep 17 00:00:00 2001 From: eshant742 Date: Fri, 14 Feb 2025 12:38:07 +0530 Subject: [PATCH 2/3] noise model done --- graphqec/__init__.py | 3 +++ tests/codes/test_base_code.py | 23 ++++++++++++----------- tests/codes/test_noise_model.py | 14 +++++++++----- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/graphqec/__init__.py b/graphqec/__init__.py index 41b133c..5428153 100644 --- a/graphqec/__init__.py +++ b/graphqec/__init__.py @@ -2,3 +2,6 @@ from .measurement import * # noqa from .stab import * # noqa from .lab import * # noqa +# graphqec/__init__.py +from .noise_model import * # noqa + diff --git a/tests/codes/test_base_code.py b/tests/codes/test_base_code.py index f879002..e83bb97 100644 --- a/tests/codes/test_base_code.py +++ b/tests/codes/test_base_code.py @@ -4,15 +4,15 @@ import stim from graphqec.codes.base_code import BaseCode -from graphqec.noise_models import DepolarizingNoiseModel +from graphqec.noise_model import DepolarizingNoiseModel class MinimalTestCode(BaseCode): def build_graph(self): """ Build a trivial graph with: - - 1 data qubit (node 0) - - 1 Z-check qubit (node 1) - - An edge connecting them with weight=1 + - 1 data qubit (node 0) + - 1 Z-check qubit (node 1) + - An edge connecting them with weight=1 """ self._graph.add_node(0, type="data") self._graph.add_node(1, type="Z-check") @@ -21,9 +21,6 @@ def build_graph(self): self._graph.add_edge(0, 1, weight=1) def test_base_code_noise_integration(): - """ - Verify that building the memory circuit inserts the correct noise instructions. - """ code = MinimalTestCode( distance=3, depolarize1_rate=0.01, @@ -31,11 +28,15 @@ def test_base_code_noise_integration(): noise_model=DepolarizingNoiseModel(), ) code.build_memory_circuit(number_of_rounds=2) + circuit = code.memory_circuit - text_diagram = circuit.to_text_diagram() + text_diagram = str(circuit) # Check for single-qubit noise - assert "DEPOLARIZE1(0.01)" in text_diagram, "Expected single-qubit noise not found in circuit." - + assert "DEPOLARIZE1(0.01)" in text_diagram, ( + "Expected single-qubit noise not found in circuit." + ) # Check for two-qubit noise - assert "DEPOLARIZE2(0.02)" in text_diagram, "Expected two-qubit noise not found in circuit." + assert "DEPOLARIZE2(0.02)" in text_diagram, ( + "Expected two-qubit noise not found in circuit." + ) diff --git a/tests/codes/test_noise_model.py b/tests/codes/test_noise_model.py index 124094c..055226c 100644 --- a/tests/codes/test_noise_model.py +++ b/tests/codes/test_noise_model.py @@ -2,28 +2,32 @@ import stim import pytest -from graphqec.noise_models import DepolarizingNoiseModel +from graphqec.noise_model import DepolarizingNoiseModel def test_depolarizing_noise_single_qubit(): - """Check single-qubit depolarizing noise.""" circuit = stim.Circuit() noise_model = DepolarizingNoiseModel() + # Apply single-qubit noise noise_model.apply_noise(circuit, "single", [0], 0.01) - text_diagram = circuit.to_text_diagram() + # Convert circuit to text using str(...) + text_diagram = str(circuit) + + # Check for the DEPOLARIZE1 instruction assert "DEPOLARIZE1(0.01) 0" in text_diagram, ( "Expected single-qubit depolarizing noise instruction missing." ) def test_depolarizing_noise_two_qubit(): - """Check two-qubit depolarizing noise.""" circuit = stim.Circuit() noise_model = DepolarizingNoiseModel() + # Apply two-qubit noise noise_model.apply_noise(circuit, "two", [0, 1], 0.02) - text_diagram = circuit.to_text_diagram() + text_diagram = str(circuit) + # Check for the DEPOLARIZE2 instruction assert "DEPOLARIZE2(0.02) 0 1" in text_diagram, ( "Expected two-qubit depolarizing noise instruction missing." ) From a6eb9104789017879fa854e1dd6ffba5b897cc1a Mon Sep 17 00:00:00 2001 From: eshant742 Date: Sat, 15 Feb 2025 21:34:51 +0530 Subject: [PATCH 3/3] updating --- graphqec/codes/base_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphqec/codes/base_code.py b/graphqec/codes/base_code.py index aea21c2..6a8a014 100644 --- a/graphqec/codes/base_code.py +++ b/graphqec/codes/base_code.py @@ -24,7 +24,7 @@ from graphqec.stab import X_check, Z_check # NEW: Import your NoiseModel and a default DepolarizingNoiseModel -from graphqec.noise_models import NoiseModel, DepolarizingNoiseModel +from graphqec.noise_model import NoiseModel, DepolarizingNoiseModel __all__ = ["BaseCode"]