Skip to content

Commit 2fd10d8

Browse files
authored
Refactor general noise (#136)
* Moving seepage for sq gates down * Rng refactor (#135) * refactor rng in noise * Clean up: remove pop0_prop and simplified crosstalk funcs to just placeholders * Hopefully fixing PR #134 for TQ gates + allowing multiple SQ gates at once * removed setters, moved scaling to builder, split seepage_prob to p1 and p2 - removed setters to pivot to focus on builder - seepage_prob -> p1_seepage_prob, p2_seepage_prob - p_crosstalk_meas -> p_meas_crosstalk - p_crosstalk_prep -> p_prep_crosstalk - p_crosstalk_meas_rescale -> p_meas_crosstalk_scale - p_crosstalk_prep_rescale -> p_prep_crosstalk_scale - removed scales for GeneralNoiseModel params but left them in builder - added average_p1_probability and average_p2_probability so users can enter average probability and get it automatically rescaled to total probability - add p_meas_max as an internal GeneralNoiseModel variable to represent the overall measurement error rate - added with_meas_probability() so users who don't want biased noise can enter just one measurement error rate * Switched WeightedSampler and related code to use BTreeMap. Added LLVM 14 errors
1 parent ae9bb86 commit 2fd10d8

File tree

23 files changed

+4255
-2367
lines changed

23 files changed

+4255
-2367
lines changed

.github/workflows/rust-test.yml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,18 +110,28 @@ jobs:
110110
if: matrix.os == 'ubuntu-latest'
111111
run: |
112112
sudo apt-get update
113-
sudo apt-get install -y llvm clang
113+
# Add LLVM 14 repository
114+
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
115+
sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main"
116+
sudo apt-get update
117+
# Install LLVM 14 specifically
118+
sudo apt-get install -y llvm-14 clang-14
119+
# Create symlinks for llc and clang
120+
sudo update-alternatives --install /usr/bin/llc llc /usr/bin/llc-14 100
121+
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-14 100
122+
# Verify installation
114123
which llc
115124
llc --version
116125
117126
- name: Install LLVM Tools (macOS)
118127
if: matrix.os == 'macos-latest'
119128
run: |
120-
brew install llvm
121-
echo "$(brew --prefix llvm)/bin" >> $GITHUB_PATH
129+
brew install llvm@14
130+
echo "$(brew --prefix llvm@14)/bin" >> $GITHUB_PATH
122131
# Make sure it's available in the current step too
123-
export PATH="$(brew --prefix llvm)/bin:$PATH"
132+
export PATH="$(brew --prefix llvm@14)/bin:$PATH"
124133
which llc
134+
llc --version
125135
126136
- name: Install LLVM Tools (Windows)
127137
if: matrix.os == 'windows-latest'

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
tmp/
2+
**/.*/settings.local.json
3+
14
# Ignore helper text in root
25
*.txt
36

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: 192 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,40 @@ struct CompileArgs {
2929
program: String,
3030
}
3131

32-
#[derive(Args)]
32+
/// Type of quantum noise model to use for simulation
33+
#[derive(PartialEq, Eq, Clone, Debug, Default)]
34+
enum NoiseModelType {
35+
/// Simple depolarizing noise model with uniform error probabilities
36+
///
37+
/// This model applies the same error probability to all operations
38+
#[default]
39+
Depolarizing,
40+
/// 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
48+
General,
49+
}
50+
51+
impl std::str::FromStr for NoiseModelType {
52+
type Err = String;
53+
54+
fn from_str(s: &str) -> Result<Self, Self::Err> {
55+
match s.to_lowercase().as_str() {
56+
"depolarizing" | "dep" => Ok(NoiseModelType::Depolarizing),
57+
"general" | "gen" => Ok(NoiseModelType::General),
58+
_ => Err(format!(
59+
"Unknown noise model type: {s}. Valid options are 'depolarizing' (dep) or 'general' (gen)"
60+
)),
61+
}
62+
}
63+
}
64+
65+
#[derive(Args, Debug)]
3366
struct RunArgs {
3467
/// Path to the quantum program (LLVM IR or JSON)
3568
program: String,
@@ -42,34 +75,150 @@ struct RunArgs {
4275
#[arg(short, long, default_value_t = 1)]
4376
workers: usize,
4477

45-
/// Depolarizing noise probability (between 0 and 1)
78+
/// Type of noise model to use (depolarizing or general)
79+
#[arg(long = "model", value_parser, default_value = "depolarizing")]
80+
noise_model: NoiseModelType,
81+
82+
/// Noise probability (between 0 and 1)
83+
/// For depolarizing model: uniform error probability
84+
/// For general model: comma-separated probabilities in order:
85+
/// `prep,meas_0,meas_1,single_qubit,two_qubit`
86+
/// Example: --noise 0.01,0.02,0.02,0.05,0.1
4687
#[arg(short = 'p', long = "noise", value_parser = parse_noise_probability)]
47-
noise_probability: Option<f64>,
88+
noise_probability: Option<String>,
4889

4990
/// Seed for random number generation (for reproducible results)
5091
#[arg(short = 'd', long)]
5192
seed: Option<u64>,
5293
}
5394

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());
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`]
100+
fn parse_noise_probability(arg: &str) -> Result<String, String> {
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"))?;
123+
124+
// Check value range
125+
if !(0.0..=1.0).contains(&prob) {
126+
return Err(format!("Probability {prob} must be between 0 and 1"));
127+
}
128+
}
129+
130+
Ok(arg.to_string())
131+
}
132+
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()
148+
} else {
149+
vec![noise_str.parse::<f64>().unwrap_or(0.0)]
60150
}
61-
Ok(prob)
62151
}
63152

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);
166+
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)
173+
}
174+
}
175+
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.
64181
fn run_program(args: &RunArgs) -> Result<(), Box<dyn Error>> {
65182
let program_path = get_program_path(&args.program)?;
66-
let prob = args.noise_probability.unwrap_or(0.0);
67-
68183
let classical_engine = setup_engine(&program_path, Some(args.shots.div_ceil(args.workers)))?;
69184

70-
let results = MonteCarloEngine::run_with_classical_engine(
185+
// Create the appropriate noise model based on user selection
186+
let noise_model: Box<dyn NoiseModel> = match args.noise_model {
187+
NoiseModelType::Depolarizing => {
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);
191+
192+
// Set seed if provided
193+
if let Some(s) = args.seed {
194+
let noise_seed = derive_seed(s, "noise_model");
195+
model.set_seed(noise_seed)?;
196+
}
197+
198+
Box::new(model)
199+
}
200+
NoiseModelType::General => {
201+
// Create a general noise model with five probabilities
202+
let (prep, meas_0, meas_1, single_qubit, two_qubit) =
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
207+
if let Some(s) = args.seed {
208+
let noise_seed = derive_seed(s, "noise_model");
209+
model.reset_with_seed(noise_seed).map_err(|e| {
210+
Box::<dyn Error>::from(format!("Failed to set noise model seed: {e}"))
211+
})?;
212+
}
213+
214+
Box::new(model)
215+
}
216+
};
217+
218+
// Use the generic approach with the selected noise model
219+
let results = MonteCarloEngine::run_with_noise_model(
71220
classical_engine,
72-
prob,
221+
noise_model,
73222
args.shots,
74223
args.workers,
75224
args.seed,
@@ -128,6 +277,7 @@ mod tests {
128277
assert_eq!(args.seed, Some(42));
129278
assert_eq!(args.shots, 100);
130279
assert_eq!(args.workers, 2);
280+
assert_eq!(args.noise_model, NoiseModelType::Depolarizing); // Default
131281
}
132282
Commands::Compile(_) => panic!("Expected Run command"),
133283
}
@@ -142,6 +292,34 @@ mod tests {
142292
assert_eq!(args.seed, None);
143293
assert_eq!(args.shots, 100);
144294
assert_eq!(args.workers, 2);
295+
assert_eq!(args.noise_model, NoiseModelType::Depolarizing); // Default
296+
}
297+
Commands::Compile(_) => panic!("Expected Run command"),
298+
}
299+
}
300+
301+
#[test]
302+
fn verify_cli_general_noise_model() {
303+
let cmd = Cli::parse_from([
304+
"pecos",
305+
"run",
306+
"program.json",
307+
"--model",
308+
"general",
309+
"-p",
310+
"0.01,0.02,0.03,0.04,0.05",
311+
"-d",
312+
"42",
313+
]);
314+
315+
match cmd.command {
316+
Commands::Run(args) => {
317+
assert_eq!(args.seed, Some(42));
318+
assert_eq!(args.noise_model, NoiseModelType::General);
319+
assert_eq!(
320+
args.noise_probability,
321+
Some("0.01,0.02,0.03,0.04,0.05".to_string())
322+
);
145323
}
146324
Commands::Compile(_) => panic!("Expected Run command"),
147325
}

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)