88from datetime import datetime
99import time
1010
11+ # Add imports for capturing the CLI invocation
12+ import sys , shlex
13+
1114T = TypeVar ('T' ) # Type of experiment result
1215
1316def sweep_results (
@@ -85,13 +88,28 @@ def plot_curve(
8588 ylabel : str ,
8689 out_path : str ,
8790 xlim : Tuple [float , float ] = None ,
88- ylim : Tuple [float , float ] = None
91+ ylim : Tuple [float , float ] = None ,
92+ comparison_data : Dict [float , List [float ]] = None ,
93+ comparison_label : str = None
8994) -> None :
90- """Plots and saves a single curve from sweep data."""
95+ """Plots and saves a single curve from sweep data.
96+
97+ Args:
98+ comparison_data: Optional second dataset for comparison (plotted with dashed lines)
99+ comparison_label: Label suffix for comparison data
100+ """
91101 plt .figure (figsize = (12 , 8 ))
92102
103+ # Plot main data with solid lines
93104 for noise , rates in data .items ():
94- plt .plot (rounds , rates , marker = 'o' , label = f'EC Noise Rate={ noise :.4f} ' )
105+ plt .plot (rounds , rates , marker = 'o' , linestyle = '-' ,
106+ label = f'EC Noise Rate={ noise :.4f} ' )
107+
108+ # Plot comparison data with dashed lines if provided
109+ if comparison_data is not None :
110+ for noise , rates in comparison_data .items ():
111+ plt .plot (rounds , rates , marker = 's' , linestyle = '--' ,
112+ label = f'EC Noise Rate={ noise :.4f} ({ comparison_label } )' )
95113
96114 plt .xlabel ('Number of Rounds' )
97115 plt .ylabel (ylabel )
@@ -122,15 +140,18 @@ def write_experiment_metadata(
122140 ec_rate_1q : float = None ,
123141 ec_rate_2q : float = None ,
124142 meas_error_rate : float = 0.0 ,
125- channel_noise_rate : float = None
143+ channel_noise_rate : float = None ,
144+ comparison_mode : bool = False
126145) -> None :
127146 """Write experiment metadata to a text file."""
128147 metadata_path = os .path .join (out_dir , "experiment_metadata.txt" )
129148
130149 with open (metadata_path , 'w' ) as f :
131150 f .write ("Tesseract EC Experiment Metadata\n " )
132151 f .write ("=" * 35 + "\n \n " )
152+ # Record when and how this script was invoked
133153 f .write (f"Timestamp: { datetime .now ().strftime ('%Y-%m-%d %H:%M:%S' )} \n " )
154+ f .write (f"Command-line: { shlex .join (sys .argv )} \n \n " )
134155 if runtime_seconds is not None :
135156 hours = int (runtime_seconds // 3600 )
136157 minutes = int ((runtime_seconds % 3600 ) // 60 )
@@ -147,6 +168,7 @@ def write_experiment_metadata(
147168 f .write (f"Encoding mode: { encoding_mode } \n " )
148169 f .write (f"Sweep channel noise: { sweep_channel_noise } \n " )
149170 f .write (f"Measurement error rate: { meas_error_rate } \n " )
171+ f .write (f"Comparison mode: { comparison_mode } \n " )
150172
151173 # Report if using fixed rates vs sweep
152174 use_fixed_rates = (ec_rate_1q is not None and ec_rate_2q is not None ) or channel_noise_rate is not None
@@ -177,6 +199,91 @@ def write_experiment_metadata(
177199
178200 print (f"Metadata saved to { metadata_path } " )
179201
202+ def _run_and_process (
203+ rounds : List [int ],
204+ noise_levels : List [float ],
205+ shots : int ,
206+ cfg_builder : Callable [[float ], NoiseCfg ],
207+ encoding_mode : Literal ['9a' , '9b' ],
208+ apply_pauli_frame : bool
209+ ) -> Tuple [Dict [float , List [float ]], Dict [float , List [float ]], Dict [float , List [float ]]]:
210+ """
211+ Helper to run the EC experiment and process its results.
212+ Returns EC acceptance, logical success, and average fidelity.
213+ """
214+ raw_results = sweep_results (
215+ run_simulation_ec_experiment ,
216+ rounds , noise_levels , shots ,
217+ cfg_builder ,
218+ apply_pauli_frame = apply_pauli_frame ,
219+ encoding_mode = encoding_mode
220+ )
221+
222+ ec_data = {
223+ noise : [t [0 ]/ shots for t in tuples ]
224+ for noise , tuples in raw_results .items ()
225+ }
226+
227+ logical_data = compute_logical_success_rate (raw_results )
228+
229+ fidelity_data = compute_average_fidelity (raw_results )
230+
231+ return ec_data , logical_data , fidelity_data
232+
233+ def plot_metric (
234+ rounds : List [int ],
235+ datasets : Dict [str , Dict [float , List [float ]]],
236+ title : str ,
237+ ylabel : str ,
238+ out_path : str ,
239+ xlim : Tuple [float , float ] = None ,
240+ ylim : Tuple [float , float ] = None ,
241+ styles : Dict [str , Tuple [str , str ]] = None
242+ ) -> None :
243+ """
244+ Plots and saves a single metric (e.g., acceptance, logical success, fidelity)
245+ from sweep data, with optional comparison.
246+ """
247+ plt .figure (figsize = (12 , 8 ))
248+ # Assign consistent colors per noise value
249+ all_noises = set ()
250+ for dataset in datasets .values ():
251+ all_noises .update (dataset .keys ())
252+ sorted_noises = sorted (all_noises )
253+ prop_cycle = plt .rcParams .get ('axes.prop_cycle' )
254+ colors = prop_cycle .by_key ().get ('color' , []) if prop_cycle else []
255+ color_map = {noise : colors [i % len (colors )] for i , noise in enumerate (sorted_noises )}
256+
257+ # Plot each labeled dataset
258+ for label , dataset in datasets .items ():
259+ linestyle , marker = styles .get (label , ('-' , 'o' )) if styles else ('-' , 'o' )
260+ for noise , rates in dataset .items ():
261+ plt .plot (
262+ rounds ,
263+ rates ,
264+ color = color_map .get (noise ),
265+ linestyle = linestyle ,
266+ marker = marker ,
267+ label = f'{ noise :.4f} ({ label } )'
268+ )
269+
270+ plt .xlabel ('Number of Rounds' )
271+ plt .ylabel (ylabel )
272+ plt .title (title )
273+ plt .grid (True )
274+ plt .legend ()
275+
276+ # Set axis limits if provided
277+ if xlim is not None :
278+ plt .xlim (xlim )
279+ if ylim is not None :
280+ plt .ylim (ylim )
281+
282+ plt .savefig (out_path )
283+ print (f"Plot saved to { out_path } " )
284+ plt .close ()
285+
286+
180287def plot_ec_experiment (
181288 rounds : List [int ],
182289 noise_levels : List [float ],
@@ -188,17 +295,17 @@ def plot_ec_experiment(
188295 ec_rate_1q : float = None ,
189296 ec_rate_2q : float = None ,
190297 meas_error_rate : float = 0.0 ,
191- channel_noise_rate : float = None
298+ channel_noise_rate : float = None ,
299+ comparison_mode : bool = False
192300) -> None :
193- """Plots both EC acceptance and logical check rates for the EC experiment ."""
301+ """Plots EC experiment curves, optionally comparing with/without Pauli-frame correction ."""
194302 start_time = time .time ()
195-
196303 # Create timestamped output directory
197304 timestamp = datetime .now ().strftime ("%Y%m%d_%H%M%S" )
198- out_dir = os .path .join (base_out_dir , f"ec_experiment_{ timestamp } " )
305+ suffix = "_comparison" if comparison_mode else ""
306+ out_dir = os .path .join (base_out_dir , f"ec_experiment_{ timestamp } { suffix } " )
199307 os .makedirs (out_dir , exist_ok = True )
200-
201- # One sweep collecting full results
308+
202309 # Determine if we're using fixed rates or sweeping
203310 use_fixed_rates = (ec_rate_1q is not None and ec_rate_2q is not None ) or channel_noise_rate is not None
204311
@@ -246,71 +353,77 @@ def plot_ec_experiment(
246353 meas_error_rate = meas_error_rate
247354 )
248355
249- raw_results = sweep_results (
250- run_simulation_ec_experiment ,
251- rounds , noise_levels , shots ,
252- cfg_builder ,
253- apply_pauli_frame = apply_pauli_frame ,
254- encoding_mode = encoding_mode
356+ # Run sweeping and processing in helper
357+ ec_main , log_main , fid_main = _run_and_process (
358+ rounds , noise_levels , shots , cfg_builder , encoding_mode , apply_pauli_frame
255359 )
256360
257- # Derive EC acceptance rate from raw results
258- ec_data = {
259- noise : [t [0 ]/ shots for t in tuples ] # ec_accept/shots
260- for noise , tuples in raw_results .items ()
261- }
361+ # Prepare datasets and styles
362+ if comparison_mode :
363+ ec_comp , log_comp , fid_comp = _run_and_process (
364+ rounds , noise_levels , shots , cfg_builder , encoding_mode , not apply_pauli_frame
365+ )
366+ labels = ['with correction' , 'without correction' ]
367+ datasets_accept = {
368+ labels [0 ]: ec_main ,
369+ labels [1 ]: ec_comp ,
370+ }
371+ datasets_logical = {
372+ labels [0 ]: log_main ,
373+ labels [1 ]: log_comp ,
374+ }
375+ datasets_fidelity = {
376+ labels [0 ]: fid_main ,
377+ labels [1 ]: fid_comp ,
378+ }
379+ styles = {
380+ labels [0 ]: ('-' , 'o' ),
381+ labels [1 ]: ('--' , 's' ),
382+ }
383+ else :
384+ label = 'with correction' if apply_pauli_frame else 'without correction'
385+ datasets_accept = {label : ec_main }
386+ datasets_logical = {label : log_main }
387+ datasets_fidelity = {label : fid_main }
388+ styles = {label : ('-' , 'o' )}
262389
263- # Set fixed axis ranges
390+ # Plot each metric
264391 max_rounds = max (rounds )
265392 x_range = (0 , max_rounds )
266-
267393 noise_type = "Channel" if sweep_channel_noise else "EC"
268- plot_curve (
269- rounds , ec_data ,
270- title = f"{ noise_type } Acceptance vs Rounds (EC Experiment)" ,
394+
395+ plot_metric (
396+ rounds , datasets_accept ,
397+ title = f"{ noise_type } Acceptance vs Rounds (EC Experiment){ ' - Comparison' if comparison_mode else '' } " ,
271398 ylabel = "EC Acceptance Rate" ,
272399 out_path = os .path .join (out_dir , 'acceptance_rates_ec_experiment.png' ),
273- xlim = x_range ,
274- ylim = (- 0.01 , 1.01 )
400+ xlim = x_range , ylim = (- 0.01 , 1.01 ), styles = styles
275401 )
276-
277- # Derive logical check rate from same raw results - normalized by acceptance
278- logical_data = compute_logical_success_rate (raw_results )
279-
280- plot_curve (
281- rounds , logical_data ,
282- title = f"Logical Check Success vs Rounds (EC Experiment) - { noise_type } Noise" ,
402+ plot_metric (
403+ rounds , datasets_logical ,
404+ title = f"Logical Check Success vs Rounds (EC Experiment) - { noise_type } Noise{ ' - Comparison' if comparison_mode else '' } " ,
283405 ylabel = "Logical Success Rate | Accepted" ,
284406 out_path = os .path .join (out_dir , 'logical_rates_ec_experiment.png' ),
285- xlim = x_range ,
286- ylim = (- 0.01 , 1.01 )
407+ xlim = x_range , ylim = (- 0.01 , 1.01 ), styles = styles
287408 )
288-
289- # Derive average fidelity from same raw results
290- fidelity_data = compute_average_fidelity (raw_results )
291-
292- plot_curve (
293- rounds , fidelity_data ,
294- title = f"Average Fidelity vs Rounds (EC Experiment) - { noise_type } Noise" ,
409+ plot_metric (
410+ rounds , datasets_fidelity ,
411+ title = f"Average Fidelity vs Rounds (EC Experiment) - { noise_type } Noise{ ' - Comparison' if comparison_mode else '' } " ,
295412 ylabel = "Average Fidelity" ,
296413 out_path = os .path .join (out_dir , 'fidelity_rates_ec_experiment.png' ),
297- xlim = x_range ,
298- ylim = (0.45 , 1.01 )
414+ xlim = x_range , ylim = (0.45 , 1.01 ), styles = styles
299415 )
300-
301- # Calculate total runtime and update metadata
302- end_time = time .time ()
303- runtime_seconds = end_time - start_time
304-
416+
305417 # Write final metadata with runtime
418+ runtime_seconds = time .time () - start_time
306419 write_experiment_metadata (
307- out_dir , rounds , noise_levels , shots ,
420+ out_dir , rounds , noise_levels , shots ,
308421 apply_pauli_frame , encoding_mode , sweep_channel_noise ,
309422 runtime_seconds = runtime_seconds ,
310- ec_rate_1q = ec_rate_1q , ec_rate_2q = ec_rate_2q ,
311- meas_error_rate = meas_error_rate , channel_noise_rate = channel_noise_rate
423+ ec_rate_1q = ec_rate_1q , ec_rate_2q = ec_rate_2q ,
424+ meas_error_rate = meas_error_rate , channel_noise_rate = channel_noise_rate ,
425+ comparison_mode = comparison_mode
312426 )
313-
314427 print (f"All experiment files saved to: { out_dir } " )
315428 print (f"Total experiment runtime: { runtime_seconds :.1f} seconds" )
316429
@@ -352,6 +465,8 @@ def main():
352465 help = 'Measurement error rate (SPAM error)' )
353466 parser .add_argument ('--channel-noise-rate' , type = float , default = None ,
354467 help = 'Channel noise rate (overrides noise-levels sweep when using --sweep-channel-noise)' )
468+ parser .add_argument ('--comparison-mode' , action = 'store_true' ,
469+ help = 'Run comparison between experiments with and without apply_pauli_frame' )
355470 args = parser .parse_args ()
356471
357472 # Use configurable values
@@ -365,7 +480,8 @@ def main():
365480 plot_ec_experiment (
366481 rounds , noise_levels , args .shots , args .out_dir ,
367482 args .apply_pauli_frame , args .encoding_mode , args .sweep_channel_noise ,
368- args .ec_rate_1q , args .ec_rate_2q , args .meas_error_rate , args .channel_noise_rate
483+ args .ec_rate_1q , args .ec_rate_2q , args .meas_error_rate , args .channel_noise_rate ,
484+ args .comparison_mode
369485 )
370486
371487if __name__ == "__main__" :
0 commit comments