11from __future__ import annotations
22
3- from collections .abc import Collection
3+ import math
4+ from collections .abc import Collection , Sequence
45
56from stim import Circuit , CircuitInstruction
67from typing_extensions import override
78
89from ..layouts import Layout
910from ..setups import (
11+ NLR ,
1012 SD6 ,
1113 SI1000 ,
1214 BiasedCircuitNoiseSetup ,
1719 PhenomenologicalNoiseSetup ,
1820 Setup ,
1921)
20- from ..setups .setup import SQ_GATES , SQ_MEASUREMENTS , SQ_RESETS , TQ_GATES
22+ from ..setups .setup import (
23+ LONG_RANGE_TQ_GATES ,
24+ SQ_GATES ,
25+ SQ_MEASUREMENTS ,
26+ SQ_RESETS ,
27+ TQ_GATES ,
28+ )
2129from .model import Model
2230from .util import biased_prefactors , grouper , idle_error_probs
2331
24- NOT_MEAS = SQ_GATES | TQ_GATES | SQ_RESETS
25- ALL_OPS = SQ_GATES | TQ_GATES | SQ_MEASUREMENTS | SQ_RESETS
32+ NOT_MEAS = SQ_GATES | TQ_GATES | LONG_RANGE_TQ_GATES | SQ_RESETS
33+ ALL_TQ_GATES = TQ_GATES | LONG_RANGE_TQ_GATES
34+ ALL_OPS = SQ_GATES | ALL_TQ_GATES | SQ_MEASUREMENTS | SQ_RESETS
2635
2736
2837class CircuitNoiseModel (Model ):
@@ -31,112 +40,109 @@ class CircuitNoiseModel(Model):
3140 @override
3241 def __getattribute__ (self , name : str ) -> object :
3342 attr = super ().__getattribute__ (name )
34-
3543 if not callable (attr ):
3644 return attr
3745
3846 if name in SQ_GATES :
47+ return self ._sq_gate_generator (name )
48+ elif name in ALL_TQ_GATES :
49+ return self ._tq_gate_generator (name )
50+ elif name in SQ_MEASUREMENTS :
51+ return self ._sq_meas_generator (name )
52+ elif name in SQ_RESETS :
53+ return self ._sq_reset_generator (name )
54+ return attr
3955
40- def sq_gate (qubits : Collection [str ]) -> Circuit :
41- inds = self .get_inds (qubits )
42- circ = Circuit ()
43-
44- circ .append (CircuitInstruction (SQ_GATES [name ], inds ))
45- if self .uniform :
46- prob : float = self .param (f"{ name } _error_prob" )
47- circ .append (CircuitInstruction ("DEPOLARIZE1" , inds , [prob ]))
48- else :
49- for qubit , ind in zip (qubits , inds ):
50- prob : float = self .param (f"{ name } _error_prob" , qubit )
51- circ .append (CircuitInstruction ("DEPOLARIZE1" , [ind ], [prob ]))
52- return circ
53-
54- return sq_gate
56+ def _sq_gate_generator (self , name : str ):
57+ def sq_gate (qubits : Collection [str ]) -> Circuit :
58+ inds = self .get_inds (qubits )
59+ circ = Circuit ()
5560
56- elif name in TQ_GATES :
61+ circ .append (CircuitInstruction (SQ_GATES [name ], inds ))
62+ if self .uniform :
63+ prob : float = self .param (f"{ name } _error_prob" )
64+ circ .append (CircuitInstruction ("DEPOLARIZE1" , inds , [prob ]))
65+ else :
66+ for qubit , ind in zip (qubits , inds ):
67+ prob : float = self .param (f"{ name } _error_prob" , qubit )
68+ circ .append (CircuitInstruction ("DEPOLARIZE1" , [ind ], [prob ]))
69+ return circ
5770
58- def tq_gate (qubits : Collection [str ]) -> Circuit :
59- if len (qubits ) % 2 != 0 :
60- raise ValueError ("Expected and even number of qubits." )
71+ return sq_gate
6172
62- inds = self .get_inds (qubits )
63- circ = Circuit ()
73+ def _tq_gate_generator (self , name : str ):
74+ def tq_gate (qubits : Collection [str ]) -> Circuit :
75+ if len (qubits ) % 2 != 0 :
76+ raise ValueError ("Expected and even number of qubits." )
6477
65- circ .append (CircuitInstruction (TQ_GATES [name ], inds ))
66- if self .uniform :
67- prob : float = self .param (f"{ name } _error_prob" )
68- circ .append (CircuitInstruction ("DEPOLARIZE2" , inds , [prob ]))
69- else :
70- for qubit_pair , ind_pair in zip (
71- grouper (qubits , 2 ), grouper (inds , 2 )
72- ):
73- prob : float = self .param (f"{ name } _error_prob" , qubit_pair )
74- circ .append (CircuitInstruction ("DEPOLARIZE2" , ind_pair , [prob ]))
75- return circ
78+ inds = self .get_inds (qubits )
79+ circ = Circuit ()
7680
77- return tq_gate
81+ circ .append (CircuitInstruction (ALL_TQ_GATES [name ], inds ))
82+ if self .uniform :
83+ prob : float = self .param (f"{ name } _error_prob" )
84+ circ .append (CircuitInstruction ("DEPOLARIZE2" , inds , [prob ]))
85+ else :
86+ for qubit_pair , ind_pair in zip (grouper (qubits , 2 ), grouper (inds , 2 )):
87+ prob : float = self .param (f"{ name } _error_prob" , qubit_pair )
88+ circ .append (CircuitInstruction ("DEPOLARIZE2" , ind_pair , [prob ]))
89+ return circ
7890
79- elif name in SQ_MEASUREMENTS :
91+ return tq_gate
8092
81- def sq_meas (qubits : Collection [str ]) -> Circuit :
82- inds = self .get_inds (qubits )
83- noise_name = "X_ERROR" if "_x" not in name else "Z_ERROR"
84- circ = Circuit ()
93+ def _sq_meas_generator (self , name : str ):
94+ def sq_meas (qubits : Collection [str ]) -> Circuit :
95+ inds = self .get_inds (qubits )
96+ noise_name = "X_ERROR" if "_x" not in name else "Z_ERROR"
97+ circ = Circuit ()
8598
86- # separates X_ERROR and MZ lines for clearer stim.Circuits and diagrams
87- if self .uniform :
88- prob : float = self .param (f"{ name } _error_prob" )
89- circ .append (CircuitInstruction (noise_name , inds , [prob ]))
90- for qubit in qubits :
91- self .add_meas (qubit )
92- if self .param ("assign_error_flag" ):
93- prob : float = self .param ("assign_error_prob" )
99+ # separates X_ERROR and MZ lines for clearer stim.Circuits and diagrams
100+ if self .uniform :
101+ prob : float = self .param (f"{ name } _error_prob" )
102+ circ .append (CircuitInstruction (noise_name , inds , [prob ]))
103+ for qubit in qubits :
104+ self .add_meas (qubit )
105+ if self .param ("assign_error_flag" ):
106+ prob : float = self .param ("assign_error_prob" )
107+ circ .append (CircuitInstruction (SQ_MEASUREMENTS [name ], inds , [prob ]))
108+ else :
109+ circ .append (CircuitInstruction (SQ_MEASUREMENTS [name ], inds ))
110+ else :
111+ for qubit , ind in zip (qubits , inds ):
112+ prob : float = self .param (f"{ name } _error_prob" , qubit )
113+ circ .append (CircuitInstruction (noise_name , [ind ], [prob ]))
114+ for qubit , ind in zip (qubits , inds ):
115+ self .add_meas (qubit )
116+ if self .param ("assign_error_flag" , qubit ):
117+ prob : float = self .param ("assign_error_prob" , qubit )
94118 circ .append (
95- CircuitInstruction (SQ_MEASUREMENTS [name ], inds , [prob ])
119+ CircuitInstruction (SQ_MEASUREMENTS [name ], [ ind ] , [prob ])
96120 )
97121 else :
98- circ .append (CircuitInstruction (SQ_MEASUREMENTS [name ], inds ))
99- else :
100- for qubit , ind in zip (qubits , inds ):
101- prob : float = self .param (f"{ name } _error_prob" , qubit )
102- circ .append (CircuitInstruction (noise_name , [ind ], [prob ]))
103- for qubit , ind in zip (qubits , inds ):
104- self .add_meas (qubit )
105- if self .param ("assign_error_flag" , qubit ):
106- prob : float = self .param ("assign_error_prob" , qubit )
107- circ .append (
108- CircuitInstruction (SQ_MEASUREMENTS [name ], [ind ], [prob ])
109- )
110- else :
111- circ .append (
112- CircuitInstruction (SQ_MEASUREMENTS [name ], [ind ])
113- )
114-
115- return circ
122+ circ .append (CircuitInstruction (SQ_MEASUREMENTS [name ], [ind ]))
116123
117- return sq_meas
118-
119- elif name in SQ_RESETS :
124+ return circ
120125
121- def sq_reset (qubits : Collection [str ]) -> Circuit :
122- inds = self .get_inds (qubits )
123- noise_name = "X_ERROR" if "_x" not in name else "Z_ERROR"
124- circ = Circuit ()
126+ return sq_meas
125127
126- circ .append (CircuitInstruction (SQ_RESETS [name ], inds ))
127- if self .uniform :
128- prob : float = self .param (f"{ name } _error_prob" )
129- circ .append (CircuitInstruction (noise_name , inds , [prob ]))
130- else :
131- for qubit , ind in zip (qubits , inds ):
132- prob : float = self .param (f"{ name } _error_prob" , qubit )
133- circ .append (CircuitInstruction (noise_name , [ind ], [prob ]))
128+ def _sq_reset_generator (self , name : str ):
129+ def sq_reset (qubits : Collection [str ]) -> Circuit :
130+ inds = self .get_inds (qubits )
131+ noise_name = "X_ERROR" if "_x" not in name else "Z_ERROR"
132+ circ = Circuit ()
134133
135- return circ
134+ circ .append (CircuitInstruction (SQ_RESETS [name ], inds ))
135+ if self .uniform :
136+ prob : float = self .param (f"{ name } _error_prob" )
137+ circ .append (CircuitInstruction (noise_name , inds , [prob ]))
138+ else :
139+ for qubit , ind in zip (qubits , inds ):
140+ prob : float = self .param (f"{ name } _error_prob" , qubit )
141+ circ .append (CircuitInstruction (noise_name , [ind ], [prob ]))
136142
137- return sq_reset
143+ return circ
138144
139- return attr
145+ return sq_reset
140146
141147 @override
142148 def idle_noise (
@@ -166,29 +172,29 @@ def __getattribute__(self, name: str) -> object:
166172 attr = super ().__getattribute__ (name )
167173
168174 if name == "swap" and callable (attr ):
175+ return self ._swap_generator (name )
169176
170- def swap (qubits : Collection [str ]) -> Circuit :
171- if len (qubits ) % 2 != 0 :
172- raise ValueError ("Expected and even number of qubits." )
177+ return attr
173178
174- inds = self .get_inds (qubits )
175- circ = Circuit ()
179+ def _swap_generator (self , name : str ):
180+ def swap (qubits : Collection [str ]) -> Circuit :
181+ if len (qubits ) % 2 != 0 :
182+ raise ValueError ("Expected and even number of qubits." )
176183
177- circ .append (CircuitInstruction ("SWAP" , inds ))
178- if self .uniform :
179- prob : float = self .param ("swap_error_prob" )
180- circ .append (CircuitInstruction ("DEPOLARIZE1" , inds , [prob ]))
181- else :
182- for qubit_pair , ind_pair in zip (
183- grouper (qubits , 2 ), grouper (inds , 2 )
184- ):
185- prob : float = self .param ("swap_error_prob" , qubit_pair )
186- circ .append (CircuitInstruction ("DEPOLARIZE1" , ind_pair , [prob ]))
187- return circ
184+ inds = self .get_inds (qubits )
185+ circ = Circuit ()
188186
189- return swap
187+ circ .append (CircuitInstruction ("SWAP" , inds ))
188+ if self .uniform :
189+ prob : float = self .param ("swap_error_prob" )
190+ circ .append (CircuitInstruction ("DEPOLARIZE1" , inds , [prob ]))
191+ else :
192+ for qubit_pair , ind_pair in zip (grouper (qubits , 2 ), grouper (inds , 2 )):
193+ prob : float = self .param ("swap_error_prob" , qubit_pair )
194+ circ .append (CircuitInstruction ("DEPOLARIZE1" , ind_pair , [prob ]))
195+ return circ
190196
191- return attr
197+ return swap
192198
193199
194200class SD6NoiseModel (CircuitNoiseModel ):
@@ -268,10 +274,10 @@ class SI1000NoiseModel(CircuitNoiseModel):
268274
269275 DEFAULT_SETUP = SI1000 ()
270276
271- def __init__ (self , qubit_inds : dict [ str , int ], setup : Setup | None = None ) -> None :
277+ def new_circuit (self ) -> None :
272278 self ._meas_or_reset_qubits : list [str ] = []
273279 self ._meas_reset_ops : list [str ] = list (SQ_MEASUREMENTS ) + list (SQ_RESETS )
274- super ().__init__ ( qubit_inds = qubit_inds , setup = setup )
280+ super ().new_circuit ( )
275281 return
276282
277283 @override
@@ -281,7 +287,7 @@ def __getattribute__(self, name: str) -> object:
281287 if callable (attr ) and (name in ALL_OPS ):
282288 if name not in self ._supported_operations :
283289 raise ValueError (
284- f"Operation { name } is not available in the SI1000 noise model."
290+ f"Operation { name } is not available in the { self . __class__ . __name__ } noise model."
285291 )
286292
287293 if name in self ._meas_reset_ops :
@@ -304,6 +310,58 @@ def flush_noise(self) -> Circuit:
304310 return circ
305311
306312
313+ class NLRNoiseModel (SI1000NoiseModel ):
314+ """
315+ The NLR noise model is defined in the following paper:
316+
317+ Beni, L. A., Higgott, O., & Shutty, N. (2025).
318+ Tesseract: A search-based decoder for quantum error correction.
319+ arXiv preprint arXiv:2503.10988.
320+
321+ which corresponds to a ``SI1000`` noise model but with higher noise
322+ strength for the two-qubit depolarizing channels after the long-range CZ gates.
323+
324+ See the documentation for the ``SI1000NoiseModel`` for more information.
325+ """
326+
327+ DEFAULT_SETUP = NLR ()
328+
329+ def __init__ (
330+ self ,
331+ qubit_inds : dict [str , int ],
332+ qubit_coords : dict [str , Sequence [float | int ]],
333+ setup : Setup | None = None ,
334+ ) -> None :
335+ self ._qubit_coords : dict [str , Sequence [float | int ]] = qubit_coords
336+ super ().__init__ (qubit_inds = qubit_inds , setup = setup )
337+ return
338+
339+ @override
340+ def __getattribute__ (self , name : str ) -> object :
341+ attr = super ().__getattribute__ (name )
342+
343+ if callable (attr ) and (name in ("cz" , "cphase" )):
344+ return self ._cphase_generator (name )
345+
346+ return attr
347+
348+ def _cphase_generator (self , name : str ):
349+ def cphase (qubits : Collection [str ]) -> Circuit :
350+ circuit = Circuit ()
351+ for pair in grouper (qubits , 2 ):
352+ distance = math .dist (
353+ self ._qubit_coords [pair [0 ]], self ._qubit_coords [pair [1 ]]
354+ )
355+ _name = name
356+ if distance > self .setup .param ("long_coupler_distance" ):
357+ _name = f"long_range_{ name } "
358+ attr = self ._tq_gate_generator (_name )
359+ circuit += attr (pair )
360+ return circuit
361+
362+ return cphase
363+
364+
307365class BiasedCircuitNoiseModel (Model ):
308366 DEFAULT_SETUP = BiasedCircuitNoiseSetup ()
309367
@@ -492,9 +550,9 @@ class T1T2NoiseModel(Model):
492550 ``T1T2NoiseModel.tick``.
493551 """
494552
495- def __init__ (self , qubit_inds : dict [ str , int ], setup : Setup | None = None ) -> None :
496- self ._durations : dict [str , float ] = {q : 0.0 for q in qubit_inds }
497- super ().__init__ ( setup = setup , qubit_inds = qubit_inds )
553+ def new_circuit (self ) -> None :
554+ self ._durations : dict [str , float ] = {q : 0.0 for q in self . _qubit_inds }
555+ super ().new_circuit ( )
498556 return
499557
500558 def _generic_gate (self , name : str , qubits : Collection [str ]) -> Circuit :
0 commit comments