Skip to content

Commit 77c5563

Browse files
committed
Switched WeightedSampler and related code to use BTreeMap. Added LLVM 14 errors
1 parent 7d3329f commit 77c5563

File tree

15 files changed

+1290
-132
lines changed

15 files changed

+1290
-132
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
**/.*/settings.local.json
2+
13
# Ignore helper text in root
24
*.txt
35

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ calls to Wasm VMs, conditional branching, and more.
2222
- Fast Simulation: Leverages a fast stabilizer simulation algorithm.
2323
- Multi-language extensions: Core functionalities implemented via Rust for performance and safety. Additional add-ons
2424
and extension support in C/C++ via Cython.
25+
- QIR Support: Execute Quantum Intermediate Representation programs (requires LLVM version 14 with the 'llc' tool).
2526

2627
## Getting Started
2728

@@ -97,6 +98,17 @@ To use PECOS in your Rust project, add the following to your `Cargo.toml`:
9798
pecos = "0.x.x" # Replace with the latest version
9899
```
99100

101+
#### Optional Dependencies
102+
103+
- **LLVM version 14**: Required for QIR (Quantum Intermediate Representation) support
104+
- Linux: `sudo apt install llvm-14`
105+
- macOS: `brew install llvm@14`
106+
- Windows: Download LLVM 14.x installer from [LLVM releases](https://releases.llvm.org/download.html#14.0.0)
107+
108+
**Note**: Only LLVM version 14.x is compatible. LLVM 15 or later versions will not work with PECOS's QIR implementation.
109+
110+
If LLVM 14 is not installed, PECOS will still function normally but QIR-related features will be disabled.
111+
100112
## Development Setup
101113

102114
If you are interested in editing or developing the code in this project, see this

crates/pecos-cli/src/main.rs

Lines changed: 201 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,30 @@ struct CompileArgs {
2929
program: String,
3030
}
3131

32-
#[derive(Args)]
32+
#[derive(PartialEq, Eq, Clone, Debug, Default)]
33+
enum NoiseModelType {
34+
/// Simple depolarizing noise model with uniform error probabilities
35+
#[default]
36+
Depolarizing,
37+
/// General noise model with configurable error probabilities
38+
General,
39+
}
40+
41+
impl std::str::FromStr for NoiseModelType {
42+
type Err = String;
43+
44+
fn from_str(s: &str) -> Result<Self, Self::Err> {
45+
match s.to_lowercase().as_str() {
46+
"depolarizing" | "dep" => Ok(NoiseModelType::Depolarizing),
47+
"general" | "gen" => Ok(NoiseModelType::General),
48+
_ => Err(format!(
49+
"Unknown noise model type: {s}. Valid options are 'depolarizing' (dep) or 'general' (gen)"
50+
)),
51+
}
52+
}
53+
}
54+
55+
#[derive(Args, Debug)]
3356
struct RunArgs {
3457
/// Path to the quantum program (LLVM IR or JSON)
3558
program: String,
@@ -42,40 +65,169 @@ struct RunArgs {
4265
#[arg(short, long, default_value_t = 1)]
4366
workers: usize,
4467

45-
/// Depolarizing noise probability (between 0 and 1)
68+
/// Type of noise model to use (depolarizing or general)
69+
#[arg(long = "model", value_parser, default_value = "depolarizing")]
70+
noise_model: NoiseModelType,
71+
72+
/// Noise probability (between 0 and 1)
73+
/// For depolarizing model: uniform error probability
74+
/// For general model: comma-separated probabilities in order:
75+
/// `prep,meas_0,meas_1,single_qubit,two_qubit`
76+
/// Example: --noise 0.01,0.02,0.02,0.05,0.1
4677
#[arg(short = 'p', long = "noise", value_parser = parse_noise_probability)]
47-
noise_probability: Option<f64>,
78+
noise_probability: Option<String>,
4879

4980
/// Seed for random number generation (for reproducible results)
5081
#[arg(short = 'd', long)]
5182
seed: Option<u64>,
5283
}
5384

54-
fn parse_noise_probability(arg: &str) -> Result<f64, String> {
55-
let prob: f64 = arg
56-
.parse()
57-
.map_err(|_| "Must be a valid floating point number")?;
58-
if !(0.0..=1.0).contains(&prob) {
59-
return Err("Noise probability must be between 0 and 1".into());
85+
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+
}
107+
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+
));
114+
}
115+
116+
// Return the original string since it's valid
117+
Ok(arg.to_string())
118+
} else {
119+
// Single probability value
120+
let prob: f64 = arg
121+
.parse()
122+
.map_err(|_| "Must be a valid floating point number")?;
123+
124+
if !(0.0..=1.0).contains(&prob) {
125+
return Err("Noise probability must be between 0 and 1".into());
126+
}
127+
128+
Ok(arg.to_string())
60129
}
61-
Ok(prob)
62130
}
63131

64132
fn run_program(args: &RunArgs) -> Result<(), Box<dyn Error>> {
65133
let program_path = get_program_path(&args.program)?;
66-
let prob = args.noise_probability.unwrap_or(0.0);
67-
68134
let classical_engine = setup_engine(&program_path, Some(args.shots.div_ceil(args.workers)))?;
69135

70-
let results = MonteCarloEngine::run_with_classical_engine(
71-
classical_engine,
72-
prob,
73-
args.shots,
74-
args.workers,
75-
args.seed,
76-
)?;
136+
// Process based on the selected noise model
137+
match args.noise_model {
138+
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);
159+
160+
// If a seed is provided, set it on the noise model
161+
if let Some(s) = args.seed {
162+
let noise_seed = derive_seed(s, "noise_model");
163+
noise_model.set_seed(noise_seed)?;
164+
}
165+
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();
176+
}
177+
NoiseModelType::General => {
178+
// For general model, we need to parse the comma-separated probabilities
179+
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+
};
77205

78-
results.print();
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
211+
if let Some(s) = args.seed {
212+
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| {
215+
Box::<dyn Error>::from(format!("Failed to set noise model seed: {e}"))
216+
})?;
217+
}
218+
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();
229+
}
230+
}
79231

80232
Ok(())
81233
}
@@ -128,6 +280,7 @@ mod tests {
128280
assert_eq!(args.seed, Some(42));
129281
assert_eq!(args.shots, 100);
130282
assert_eq!(args.workers, 2);
283+
assert_eq!(args.noise_model, NoiseModelType::Depolarizing); // Default
131284
}
132285
Commands::Compile(_) => panic!("Expected Run command"),
133286
}
@@ -142,6 +295,34 @@ mod tests {
142295
assert_eq!(args.seed, None);
143296
assert_eq!(args.shots, 100);
144297
assert_eq!(args.workers, 2);
298+
assert_eq!(args.noise_model, NoiseModelType::Depolarizing); // Default
299+
}
300+
Commands::Compile(_) => panic!("Expected Run command"),
301+
}
302+
}
303+
304+
#[test]
305+
fn verify_cli_general_noise_model() {
306+
let cmd = Cli::parse_from([
307+
"pecos",
308+
"run",
309+
"program.json",
310+
"--model",
311+
"general",
312+
"-p",
313+
"0.01,0.02,0.03,0.04,0.05",
314+
"-d",
315+
"42",
316+
]);
317+
318+
match cmd.command {
319+
Commands::Run(args) => {
320+
assert_eq!(args.seed, Some(42));
321+
assert_eq!(args.noise_model, NoiseModelType::General);
322+
assert_eq!(
323+
args.noise_probability,
324+
Some("0.01,0.02,0.03,0.04,0.05".to_string())
325+
);
145326
}
146327
Commands::Compile(_) => panic!("Expected Run command"),
147328
}

crates/pecos-engines/QIR_RUNTIME.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,32 @@
22

33
The QIR (Quantum Intermediate Representation) compiler in PECOS uses a Rust runtime library to implement quantum operations. This library is automatically built by the `build.rs` script in the `pecos-engines` crate.
44

5+
## Requirements
6+
7+
To use QIR functionality, you need:
8+
9+
- **LLVM version 14 specifically**:
10+
- On Linux: Install using your package manager (e.g., `sudo apt install llvm-14`)
11+
- On macOS: Install using Homebrew (`brew install llvm@14`)
12+
- On Windows: Download and install LLVM 14.x from the [LLVM website](https://releases.llvm.org/download.html#14.0.0)
13+
14+
- **Required tools**:
15+
- Linux/macOS: The `llc` compiler tool must be in your PATH
16+
- Windows: The `clang` compiler must be in your PATH
17+
18+
**Note**: PECOS requires LLVM version 14.x specifically, not newer versions. LLVM 15 or later versions are not compatible with PECOS's QIR implementation.
19+
20+
If LLVM 14 is not installed or the required tools aren't found, QIR functionality will be disabled but the rest of PECOS will continue to work normally.
21+
522
## How It Works
623

724
The `build.rs` script:
825

926
1. Runs automatically when building the `pecos-engines` crate
10-
2. Checks if the QIR runtime library needs to be rebuilt
11-
3. Builds the library only if necessary (if source files have changed)
12-
4. Places the built library in both `target/debug` and `target/release` directories
27+
2. Checks for LLVM 14+ dependencies
28+
3. Checks if the QIR runtime library needs to be rebuilt
29+
4. Builds the library only if necessary (if source files have changed)
30+
5. Places the built library in both `target/debug` and `target/release` directories
1331

1432
When the QIR compiler runs, it looks for the pre-built library in these locations. If the library is not found, the compiler will attempt to build it by running `cargo build -p pecos-engines` before raising an error.
1533

0 commit comments

Comments
 (0)