@@ -29,12 +29,22 @@ struct CompileArgs {
2929 program : String ,
3030}
3131
32+ /// Type of quantum noise model to use for simulation
3233#[ derive( PartialEq , Eq , Clone , Debug , Default ) ]
3334enum NoiseModelType {
3435 /// Simple depolarizing noise model with uniform error probabilities
36+ ///
37+ /// This model applies the same error probability to all operations
3538 #[ default]
3639 Depolarizing ,
3740 /// General noise model with configurable error probabilities
41+ ///
42+ /// This model allows setting different error probabilities for:
43+ /// - state preparation
44+ /// - measurement of |0⟩ state
45+ /// - measurement of |1⟩ state
46+ /// - single-qubit gates
47+ /// - two-qubit gates
3848 General ,
3949}
4050
@@ -82,152 +92,139 @@ struct RunArgs {
8292 seed : Option < u64 > ,
8393}
8494
95+ /// Parse noise probability specification from command line argument
96+ ///
97+ /// For a depolarizing model, a single probability is expected: "0.01"
98+ /// For a general model, five probabilities are expected: "0.01,0.02,0.02,0.05,0.1"
99+ /// representing [prep, `meas_0`, `meas_1`, `single_qubit`, `two_qubit`]
85100fn parse_noise_probability ( arg : & str ) -> Result < String , String > {
86- // Check if it's a comma-separated list
87- if arg. contains ( ',' ) {
88- // Split by comma and parse each value
89- let probs: Result < Vec < f64 > , _ > = arg
90- . split ( ',' )
91- . map ( |s| {
92- s. trim ( ) . parse :: < f64 > ( ) . map_err ( |_| {
93- format ! (
94- "Invalid probability value '{s}': must be a valid floating point number"
95- )
96- } )
97- } )
98- . collect ( ) ;
99-
100- // Check if all values are valid probabilities
101- let probs = probs?;
102- for prob in & probs {
103- if !( 0.0 ..=1.0 ) . contains ( prob) {
104- return Err ( format ! ( "Noise probability {prob} must be between 0 and 1" ) ) ;
105- }
106- }
101+ // Split string into values (either a single value or comma-separated list)
102+ let values: Vec < & str > = if arg. contains ( ',' ) {
103+ arg. split ( ',' ) . collect ( )
104+ } else {
105+ vec ! [ arg]
106+ } ;
107+
108+ // Check number of values
109+ if values. len ( ) != 1 && values. len ( ) != 5 {
110+ return Err ( format ! (
111+ "Expected 1 or 5 probabilities, got {}" ,
112+ values. len( )
113+ ) ) ;
114+ }
115+
116+ // Validate each probability value
117+ for s in & values {
118+ // Parse and validate numeric value
119+ let prob = s
120+ . trim ( )
121+ . parse :: < f64 > ( )
122+ . map_err ( |_| format ! ( "Invalid value '{s}': not a valid number" ) ) ?;
107123
108- // For general noise model, we expect 5 probabilities
109- if probs. len ( ) != 5 && probs. len ( ) != 1 {
110- return Err ( format ! (
111- "Expected either 1 probability for depolarizing model or 5 probabilities for general model, got {}" ,
112- probs. len( )
113- ) ) ;
124+ // Check value range
125+ if !( 0.0 ..=1.0 ) . contains ( & prob) {
126+ return Err ( format ! ( "Probability {prob} must be between 0 and 1" ) ) ;
114127 }
128+ }
129+
130+ Ok ( arg. to_string ( ) )
131+ }
115132
116- // Return the original string since it's valid
117- Ok ( arg. to_string ( ) )
133+ /// Extract probability values from noise specification string
134+ ///
135+ /// Handles both single value and comma-separated formats, with safe defaults
136+ fn parse_noise_values ( noise_str_opt : Option < & String > ) -> Vec < f64 > {
137+ // Default to 0.0 if no string provided
138+ let Some ( noise_str) = noise_str_opt else {
139+ return vec ! [ 0.0 ] ;
140+ } ;
141+
142+ // Parse either comma-separated or single value
143+ if noise_str. contains ( ',' ) {
144+ noise_str
145+ . split ( ',' )
146+ . map ( |s| s. trim ( ) . parse :: < f64 > ( ) . unwrap_or ( 0.0 ) )
147+ . collect ( )
118148 } else {
119- // Single probability value
120- let prob: f64 = arg
121- . parse ( )
122- . map_err ( |_| "Must be a valid floating point number" ) ?;
149+ vec ! [ noise_str. parse:: <f64 >( ) . unwrap_or( 0.0 ) ]
150+ }
151+ }
123152
124- if !( 0.0 ..=1.0 ) . contains ( & prob) {
125- return Err ( "Noise probability must be between 0 and 1" . into ( ) ) ;
126- }
153+ /// Parse a single probability value for depolarizing noise model
154+ ///
155+ /// Takes the first probability value if multiple are provided
156+ fn parse_depolarizing_noise_probability ( noise_str_opt : Option < & String > ) -> f64 {
157+ parse_noise_values ( noise_str_opt) [ 0 ] // Always has at least one value
158+ }
159+
160+ /// Parse five probability values for general noise model
161+ ///
162+ /// Returns a tuple of five probabilities: (prep, `meas_0`, `meas_1`, `single_qubit`, `two_qubit`)
163+ /// If a single value is provided, it's used for all five parameters
164+ fn parse_general_noise_probabilities ( noise_str_opt : Option < & String > ) -> ( f64 , f64 , f64 , f64 , f64 ) {
165+ let probs = parse_noise_values ( noise_str_opt) ;
127166
128- Ok ( arg. to_string ( ) )
167+ if probs. len ( ) == 5 {
168+ ( probs[ 0 ] , probs[ 1 ] , probs[ 2 ] , probs[ 3 ] , probs[ 4 ] )
169+ } else {
170+ // Use the first value for all parameters
171+ let p = probs[ 0 ] ;
172+ ( p, p, p, p, p)
129173 }
130174}
131175
176+ /// Run a quantum program with the specified arguments
177+ ///
178+ /// This function sets up the appropriate engines and noise models based on
179+ /// the command line arguments, then runs the specified program and outputs
180+ /// the results.
132181fn run_program ( args : & RunArgs ) -> Result < ( ) , Box < dyn Error > > {
133182 let program_path = get_program_path ( & args. program ) ?;
134183 let classical_engine = setup_engine ( & program_path, Some ( args. shots . div_ceil ( args. workers ) ) ) ?;
135184
136- // Process based on the selected noise model
137- match args. noise_model {
185+ // Create the appropriate noise model based on user selection
186+ let noise_model : Box < dyn NoiseModel > = match args. noise_model {
138187 NoiseModelType :: Depolarizing => {
139- // Single noise probability for depolarizing model
140- let prob = if let Some ( noise_str) = & args. noise_probability {
141- // If it contains commas, take the first value
142- if noise_str. contains ( ',' ) {
143- noise_str
144- . split ( ',' )
145- . next ( )
146- . unwrap ( )
147- . trim ( )
148- . parse :: < f64 > ( )
149- . unwrap_or ( 0.0 )
150- } else {
151- noise_str. parse :: < f64 > ( ) . unwrap_or ( 0.0 )
152- }
153- } else {
154- 0.0
155- } ;
156-
157- // Create a depolarizing noise model
158- let mut noise_model = DepolarizingNoiseModel :: new_uniform ( prob) ;
188+ // Create a depolarizing noise model with single probability
189+ let prob = parse_depolarizing_noise_probability ( args. noise_probability . as_ref ( ) ) ;
190+ let mut model = DepolarizingNoiseModel :: new_uniform ( prob) ;
159191
160- // If a seed is provided, set it on the noise model
192+ // Set seed if provided
161193 if let Some ( s) = args. seed {
162194 let noise_seed = derive_seed ( s, "noise_model" ) ;
163- noise_model . set_seed ( noise_seed) ?;
195+ model . set_seed ( noise_seed) ?;
164196 }
165197
166- // Use the generic approach with noise model
167- let results = MonteCarloEngine :: run_with_noise_model (
168- classical_engine,
169- Box :: new ( noise_model) ,
170- args. shots ,
171- args. workers ,
172- args. seed ,
173- ) ?;
174-
175- results. print ( ) ;
198+ Box :: new ( model)
176199 }
177200 NoiseModelType :: General => {
178- // For general model, we need to parse the comma-separated probabilities
201+ // Create a general noise model with five probabilities
179202 let ( prep, meas_0, meas_1, single_qubit, two_qubit) =
180- if let Some ( noise_str) = & args. noise_probability {
181- if noise_str. contains ( ',' ) {
182- // Parse the comma-separated values
183- let probs: Vec < f64 > = noise_str
184- . split ( ',' )
185- . map ( |s| s. trim ( ) . parse :: < f64 > ( ) . unwrap_or ( 0.0 ) )
186- . collect ( ) ;
187-
188- // We should already have validated the length in the parser
189- if probs. len ( ) == 5 {
190- ( probs[ 0 ] , probs[ 1 ] , probs[ 2 ] , probs[ 3 ] , probs[ 4 ] )
191- } else {
192- // Use the first value for all if only one value is provided
193- let p = probs[ 0 ] ;
194- ( p, p, p, p, p)
195- }
196- } else {
197- // Single probability value - use for all parameters
198- let p = noise_str. parse :: < f64 > ( ) . unwrap_or ( 0.0 ) ;
199- ( p, p, p, p, p)
200- }
201- } else {
202- // Default: no noise
203- ( 0.0 , 0.0 , 0.0 , 0.0 , 0.0 )
204- } ;
205-
206- // Create the general noise model
207- let mut noise_model =
208- GeneralNoiseModel :: new ( prep, meas_0, meas_1, single_qubit, two_qubit) ;
209-
210- // If a seed is provided, set it on the noise model
203+ parse_general_noise_probabilities ( args. noise_probability . as_ref ( ) ) ;
204+ let mut model = GeneralNoiseModel :: new ( prep, meas_0, meas_1, single_qubit, two_qubit) ;
205+
206+ // Set seed if provided
211207 if let Some ( s) = args. seed {
212208 let noise_seed = derive_seed ( s, "noise_model" ) ;
213- // We can now silence the non-deterministic warning since we've fixed that issue
214- noise_model. reset_with_seed ( noise_seed) . map_err ( |e| {
209+ model. reset_with_seed ( noise_seed) . map_err ( |e| {
215210 Box :: < dyn Error > :: from ( format ! ( "Failed to set noise model seed: {e}" ) )
216211 } ) ?;
217212 }
218213
219- // Use the generic function with the general noise model
220- let results = MonteCarloEngine :: run_with_noise_model (
221- classical_engine,
222- Box :: new ( noise_model) ,
223- args. shots ,
224- args. workers ,
225- args. seed ,
226- ) ?;
227-
228- results. print ( ) ;
214+ Box :: new ( model)
229215 }
230- }
216+ } ;
217+
218+ // Use the generic approach with the selected noise model
219+ let results = MonteCarloEngine :: run_with_noise_model (
220+ classical_engine,
221+ noise_model,
222+ args. shots ,
223+ args. workers ,
224+ args. seed ,
225+ ) ?;
226+
227+ results. print ( ) ;
231228
232229 Ok ( ( ) )
233230}
0 commit comments