Skip to content

Commit 0f1e3e4

Browse files
committed
code simplification
1 parent 77c5563 commit 0f1e3e4

File tree

7 files changed

+592
-595
lines changed

7 files changed

+592
-595
lines changed

crates/pecos-cli/src/main.rs

Lines changed: 113 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -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)]
3334
enum 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`]
85100
fn 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.
132181
fn 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

Comments
 (0)