microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/replace-qsharp-with-qdk-python-tests

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/pip/src/qir_simulation/cpu_simulators.rs

398lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::qir_simulation::{
5 NoiseConfig, QirInstruction, QirInstructionId, adaptive_program_from_pydict,
6 unbind_noise_config,
7};
8use pyo3::{IntoPyObjectExt, exceptions::PyValueError, prelude::*, types::PyList};
9use pyo3::{PyResult, pyfunction, types::PyDict};
10use qdk_simulators::{
11 MeasurementResult, Simulator,
12 bytecode::{self, runtime::run_shot as adaptive_run_shot},
13 cpu_full_state_simulator::{NoiselessSimulator, NoisySimulator},
14 noise_config::{self, CumulativeNoiseConfig},
15 stabilizer_simulator::StabilizerSimulator,
16};
17use rand::{Rng, SeedableRng, rngs::StdRng};
18use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
19use std::{fmt::Write, sync::Arc};
20
21#[pyfunction]
22pub fn run_clifford<'py>(
23 py: Python<'py>,
24 input: &Bound<'py, PyList>,
25 num_qubits: u32,
26 num_results: u32,
27 shots: u32,
28 noise_config: Option<&Bound<'py, NoiseConfig>>,
29 seed: Option<u32>,
30) -> PyResult<Py<PyAny>> {
31 let make_simulator = |num_qubits, num_results, seed, noise| {
32 StabilizerSimulator::new(num_qubits as usize, num_results as usize, seed, noise)
33 };
34 py_run(
35 py,
36 input,
37 num_qubits,
38 num_results,
39 shots,
40 noise_config,
41 seed,
42 make_simulator,
43 )
44}
45
46#[pyfunction]
47pub fn run_cpu_full_state<'py>(
48 py: Python<'py>,
49 input: &Bound<'py, PyList>,
50 num_qubits: u32,
51 num_results: u32,
52 shots: u32,
53 noise_config: Option<&Bound<'py, NoiseConfig>>,
54 seed: Option<u32>,
55) -> PyResult<Py<PyAny>> {
56 use qdk_simulators::cpu_full_state_simulator::noise::Fault;
57 if noise_config.is_some() {
58 let make_simulator = |num_qubits, num_results, seed, noise| {
59 NoisySimulator::new(num_qubits as usize, num_results as usize, seed, noise)
60 };
61 py_run(
62 py,
63 input,
64 num_qubits,
65 num_results,
66 shots,
67 noise_config,
68 seed,
69 make_simulator,
70 )
71 } else {
72 let make_simulator =
73 |num_qubits, num_results, seed, _noise: Arc<CumulativeNoiseConfig<Fault>>| {
74 NoiselessSimulator::new(num_qubits as usize, num_results as usize, seed, ())
75 };
76 py_run(
77 py,
78 input,
79 num_qubits,
80 num_results,
81 shots,
82 noise_config,
83 seed,
84 make_simulator,
85 )
86 }
87}
88
89#[allow(clippy::too_many_arguments)]
90fn py_run<'py, SimulatorBuilder, Noise, S>(
91 py: Python<'py>,
92 input: &Bound<'py, PyList>,
93 num_qubits: u32,
94 num_results: u32,
95 shots: u32,
96 noise_config: Option<&Bound<'py, NoiseConfig>>,
97 seed: Option<u32>,
98 make_simulator: SimulatorBuilder,
99) -> PyResult<Py<PyAny>>
100where
101 SimulatorBuilder: Fn(u32, u32, u32, Arc<Noise>) -> S,
102 SimulatorBuilder: Send + Sync,
103 Noise: From<qdk_simulators::noise_config::NoiseConfig<f64, f64>> + Send + Sync,
104 S: Simulator,
105{
106 // Convert Python list to Vec<QirInstruction>.
107 let mut instructions: Vec<QirInstruction> = Vec::with_capacity(input.len());
108 for item in input.iter() {
109 let item: QirInstruction = item
110 .extract()
111 .map_err(|e| PyValueError::new_err(format!("expected QirInstruction: {e}")))?;
112 instructions.push(item);
113 }
114
115 // Convert NoiseConfig to a rust NoiseConfig.
116 let noise: qdk_simulators::noise_config::NoiseConfig<f64, f64> =
117 if let Some(noise_config) = noise_config {
118 unbind_noise_config(py, noise_config)
119 } else {
120 qdk_simulators::noise_config::NoiseConfig::NOISELESS
121 };
122
123 // Run the simulation.
124 let output = run(
125 &instructions,
126 num_qubits,
127 num_results,
128 shots,
129 seed,
130 noise,
131 make_simulator,
132 );
133
134 // Convert results back to Python.
135 let mut array = Vec::with_capacity(shots as usize);
136 for val in output {
137 array.push(
138 val.into_py_any(py).map_err(|e| {
139 PyValueError::new_err(format!("failed to create Python string: {e}"))
140 })?,
141 );
142 }
143
144 PyList::new(py, array)
145 .map_err(|e| PyValueError::new_err(format!("failed to create Python list: {e}")))?
146 .into_py_any(py)
147}
148
149fn run<SimulatorBuilder, Noise, S>(
150 instructions: &[QirInstruction],
151 num_qubits: u32,
152 num_results: u32,
153 shots: u32,
154 seed: Option<u32>,
155 noise: noise_config::NoiseConfig<f64, f64>,
156 make_simulator: SimulatorBuilder,
157) -> Vec<String>
158where
159 SimulatorBuilder: Fn(u32, u32, u32, Arc<Noise>) -> S,
160 SimulatorBuilder: Send + Sync,
161 Noise: From<noise_config::NoiseConfig<f64, f64>> + Send + Sync,
162 S: Simulator,
163{
164 let noise: Noise = noise.into();
165 let noise = Arc::new(noise);
166
167 // Create a random number generator to generate the seed for each individual shot.
168 let mut rng = if let Some(seed) = seed {
169 StdRng::seed_from_u64(seed.into())
170 } else {
171 StdRng::from_entropy()
172 };
173
174 // run the shots
175 let output = (0..shots)
176 .map(|_| rng.r#gen())
177 .collect::<Vec<u32>>()
178 .par_iter()
179 .map(|shot_seed| {
180 let mut simulator = make_simulator(num_qubits, num_results, *shot_seed, noise.clone());
181 run_shot(instructions, &mut simulator);
182 simulator.take_measurements()
183 })
184 .collect::<Vec<_>>();
185
186 // Convert results to a list of strings.
187 let mut values = Vec::with_capacity(shots as usize);
188 for shot_result in output {
189 let mut buffer = String::with_capacity(shot_result.len());
190 for measurement in shot_result {
191 match measurement {
192 MeasurementResult::Zero => write!(&mut buffer, "0").expect("write should succeed"),
193 MeasurementResult::One => write!(&mut buffer, "1").expect("write should succeed"),
194 MeasurementResult::Loss => write!(&mut buffer, "L").expect("write should succeed"),
195 }
196 }
197 values.push(buffer);
198 }
199 values
200}
201
202fn run_shot<S: Simulator>(instructions: &[QirInstruction], sim: &mut S) {
203 for qir_inst in instructions {
204 match qir_inst {
205 QirInstruction::OneQubitGate(id, qubit) => match id {
206 QirInstructionId::I => {} // Identity gate is a no-op
207 QirInstructionId::H => sim.h(*qubit as usize),
208 QirInstructionId::X => sim.x(*qubit as usize),
209 QirInstructionId::Y => sim.y(*qubit as usize),
210 QirInstructionId::Z => sim.z(*qubit as usize),
211 QirInstructionId::S => sim.s(*qubit as usize),
212 QirInstructionId::SAdj => sim.s_adj(*qubit as usize),
213 QirInstructionId::SX => sim.sx(*qubit as usize),
214 QirInstructionId::SXAdj => sim.sx_adj(*qubit as usize),
215 QirInstructionId::T => sim.t(*qubit as usize),
216 QirInstructionId::TAdj => sim.t_adj(*qubit as usize),
217 QirInstructionId::Move => sim.mov(*qubit as usize),
218 QirInstructionId::RESET => sim.resetz(*qubit as usize),
219 _ => panic!("unsupported one-qubit gate: {id:?}"),
220 },
221 QirInstruction::TwoQubitGate(id, q1, q2) => match id {
222 QirInstructionId::CX => sim.cx(*q1 as usize, *q2 as usize),
223 QirInstructionId::CY => sim.cy(*q1 as usize, *q2 as usize),
224 QirInstructionId::CZ => sim.cz(*q1 as usize, *q2 as usize),
225 QirInstructionId::MZ | QirInstructionId::M => sim.mz(*q1 as usize, *q2 as usize),
226 QirInstructionId::MResetZ => sim.mresetz(*q1 as usize, *q2 as usize),
227 QirInstructionId::SWAP => sim.swap(*q1 as usize, *q2 as usize),
228 _ => panic!("unsupported two-qubits gate: {id:?}"),
229 },
230 QirInstruction::OneQubitRotationGate(id, angle, qubit) => match id {
231 QirInstructionId::RX => sim.rx(*angle, *qubit as usize),
232 QirInstructionId::RY => sim.ry(*angle, *qubit as usize),
233 QirInstructionId::RZ => sim.rz(*angle, *qubit as usize),
234 _ => {
235 panic!("unsupported one-qubit rotation gate: {id:?}");
236 }
237 },
238 QirInstruction::TwoQubitRotationGate(id, angle, qubit1, qubit2) => match id {
239 QirInstructionId::RXX => sim.rxx(*angle, *qubit1 as usize, *qubit2 as usize),
240 QirInstructionId::RYY => sim.ryy(*angle, *qubit1 as usize, *qubit2 as usize),
241 QirInstructionId::RZZ => sim.rzz(*angle, *qubit1 as usize, *qubit2 as usize),
242 _ => panic!("unsupported two-qubit rotation gate: {id:?}"),
243 },
244 QirInstruction::CorrelatedNoise(_id, intrinsic_id, qubits) => {
245 sim.correlated_noise_intrinsic(
246 *intrinsic_id,
247 &qubits.iter().map(|q| *q as usize).collect::<Vec<_>>(),
248 );
249 }
250 QirInstruction::OutputRecording(_id, _s, _tag) => {
251 // Ignore for now.
252 }
253 QirInstruction::ThreeQubitGate(..) => {
254 panic!("unsupported instruction: {qir_inst:?}")
255 }
256 }
257 }
258}
259
260// ---------------------------------------------------------------------------
261// Adaptive Profile CPU simulation
262// ---------------------------------------------------------------------------
263
264#[pyfunction]
265#[allow(clippy::too_many_arguments)]
266pub fn run_cpu_adaptive<'py>(
267 py: Python<'py>,
268 input: &Bound<'py, PyDict>,
269 shots: u32,
270 noise_config: Option<&Bound<'py, NoiseConfig>>,
271 seed: Option<u32>,
272) -> PyResult<Py<PyAny>> {
273 use qdk_simulators::cpu_full_state_simulator::noise::Fault;
274
275 let program: bytecode::AdaptiveProgram<u64> = adaptive_program_from_pydict(input)?;
276
277 let noise: noise_config::NoiseConfig<f64, f64> = if let Some(nc) = noise_config {
278 unbind_noise_config(py, nc)
279 } else {
280 noise_config::NoiseConfig::NOISELESS
281 };
282
283 let output = if noise_config.is_some() {
284 let make_simulator =
285 |num_qubits, num_results, seed, noise: Arc<CumulativeNoiseConfig<Fault>>| {
286 NoisySimulator::new(num_qubits, num_results, seed, noise)
287 };
288 run_adaptive(&program, shots, seed, noise, make_simulator)
289 } else {
290 let make_simulator =
291 |num_qubits, num_results, seed, _noise: Arc<CumulativeNoiseConfig<Fault>>| {
292 NoiselessSimulator::new(num_qubits, num_results, seed, ())
293 };
294 run_adaptive(&program, shots, seed, noise, make_simulator)
295 };
296
297 let mut array = Vec::with_capacity(shots as usize);
298 for val in output {
299 array.push(
300 val.into_py_any(py).map_err(|e| {
301 PyValueError::new_err(format!("failed to create Python string: {e}"))
302 })?,
303 );
304 }
305
306 PyList::new(py, array)
307 .map_err(|e| PyValueError::new_err(format!("failed to create Python list: {e}")))?
308 .into_py_any(py)
309}
310
311#[pyfunction]
312#[allow(clippy::too_many_arguments)]
313pub fn run_clifford_adaptive<'py>(
314 py: Python<'py>,
315 input: &Bound<'py, PyDict>,
316 shots: u32,
317 noise_config: Option<&Bound<'py, NoiseConfig>>,
318 seed: Option<u32>,
319) -> PyResult<Py<PyAny>> {
320 use qdk_simulators::stabilizer_simulator::noise::Fault;
321
322 let program: bytecode::AdaptiveProgram<u64> = adaptive_program_from_pydict(input)?;
323
324 let noise: noise_config::NoiseConfig<f64, f64> = if let Some(nc) = noise_config {
325 unbind_noise_config(py, nc)
326 } else {
327 noise_config::NoiseConfig::NOISELESS
328 };
329
330 let make_simulator =
331 |num_qubits, num_results, seed, noise: Arc<CumulativeNoiseConfig<Fault>>| {
332 StabilizerSimulator::new(num_qubits, num_results, seed, noise)
333 };
334 let output = run_adaptive(&program, shots, seed, noise, make_simulator);
335
336 let mut array = Vec::with_capacity(shots as usize);
337 for val in output {
338 array.push(
339 val.into_py_any(py).map_err(|e| {
340 PyValueError::new_err(format!("failed to create Python string: {e}"))
341 })?,
342 );
343 }
344
345 PyList::new(py, array)
346 .map_err(|e| PyValueError::new_err(format!("failed to create Python list: {e}")))?
347 .into_py_any(py)
348}
349
350fn run_adaptive<SimulatorBuilder, Noise, S>(
351 program: &bytecode::AdaptiveProgram<u64>,
352 shots: u32,
353 seed: Option<u32>,
354 noise: noise_config::NoiseConfig<f64, f64>,
355 make_simulator: SimulatorBuilder,
356) -> Vec<String>
357where
358 SimulatorBuilder: Fn(usize, usize, u32, Arc<Noise>) -> S + Send + Sync,
359 Noise: From<noise_config::NoiseConfig<f64, f64>> + Send + Sync,
360 S: Simulator,
361{
362 let noise: Noise = noise.into();
363 let noise = Arc::new(noise);
364
365 let num_qubits = program.num_qubits as usize;
366 let num_results = program.num_results as usize;
367
368 let mut rng = if let Some(seed) = seed {
369 StdRng::seed_from_u64(seed.into())
370 } else {
371 StdRng::from_entropy()
372 };
373
374 let output = (0..shots)
375 .map(|_| rng.r#gen())
376 .collect::<Vec<u32>>()
377 .par_iter()
378 .map(|shot_seed| {
379 let mut simulator = make_simulator(num_qubits, num_results, *shot_seed, noise.clone());
380 adaptive_run_shot(program, &mut simulator);
381 simulator.take_measurements()
382 })
383 .collect::<Vec<_>>();
384
385 let mut values = Vec::with_capacity(shots as usize);
386 for shot_result in output {
387 let mut buffer = String::with_capacity(shot_result.len());
388 for measurement in shot_result {
389 match measurement {
390 MeasurementResult::Zero => write!(&mut buffer, "0").expect("write should succeed"),
391 MeasurementResult::One => write!(&mut buffer, "1").expect("write should succeed"),
392 MeasurementResult::Loss => write!(&mut buffer, "L").expect("write should succeed"),
393 }
394 }
395 values.push(buffer);
396 }
397 values
398}
399