microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
7421e7dd1015dcbd940bf843d33583470de580ea

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/src/noisy_simulator.rs

394lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! This module is a thin `PyO3` wrapper around the rust `noisy_simulator` crate.
5
6use noisy_simulator::{ComplexVector, NoisySimulator, SquareMatrix};
7use num_complex::Complex;
8use pyo3::{exceptions::PyException, prelude::*};
9type PythonMatrix = Vec<Vec<Complex<f64>>>;
10
11pub(crate) fn register_noisy_simulator_submodule<'a>(
12 py: Python<'a>,
13 m: &Bound<'a, PyModule>,
14) -> PyResult<()> {
15 m.add("NoisySimulatorError", py.get_type::<NoisySimulatorError>())?;
16 m.add_class::<Operation>()?;
17 m.add_class::<Instrument>()?;
18 m.add_class::<DensityMatrixSimulator>()?;
19 m.add_class::<StateVectorSimulator>()?;
20 Ok(())
21}
22
23/// Performance Warning:
24/// nalgebra stores its matrices in column major order, and we want to send
25/// them from Python in row major order, this means that there will be lots of
26/// cache-misses in the conversion from one format to another.
27///
28/// This function is only used on a non-critical path for performance. Namely,
29/// the input to the simulator to set it up, and getting the final output.
30/// This warning is just to avoid any performance accidents in the future.
31fn python_matrix_to_nalgebra_matrix(matrix: PythonMatrix) -> PyResult<SquareMatrix> {
32 let nrows = matrix.len();
33 let ncols = matrix[0].len();
34 // Check that matrix is well formed.
35 for row in &matrix {
36 if ncols != row.len() {
37 return Err(NoisySimulatorError::new_err(
38 "ill formed matrix, all rows should be the same length".to_string(),
39 ));
40 }
41 }
42 // Move matrix into a linear container.
43 let mut data = Vec::with_capacity(nrows * ncols);
44 for mut row in matrix {
45 data.append(&mut row);
46 }
47 Ok(SquareMatrix::from_row_iterator(nrows, ncols, data))
48}
49
50fn nalgebra_matrix_to_python_matrix(matrix: &SquareMatrix) -> PythonMatrix {
51 // Performance note: Because of the performance optimization in
52 // noisy_simulator/src/operation.rs/Operation::new, the simulator stores its matrices
53 // transposed. When we give them back to python, we need to transpose them back,
54 // that's why we `python_row.extend(nalgebra_col.iter())`.
55
56 let (nrows, ncols) = matrix.shape();
57 let mut python_list = Vec::with_capacity(ncols);
58
59 for nalgebra_col in matrix.column_iter() {
60 let mut python_row = Vec::with_capacity(nrows);
61 python_row.extend(nalgebra_col.iter());
62 python_list.push(python_row);
63 }
64
65 python_list
66}
67
68pyo3::create_exception!(qsharp.noisy_sim, NoisySimulatorError, PyException);
69
70#[pyclass]
71#[derive(Clone)]
72pub(crate) struct Operation(noisy_simulator::Operation);
73
74#[pymethods]
75impl Operation {
76 #[new]
77 pub fn new(kraus_operators: Vec<PythonMatrix>) -> PyResult<Self> {
78 let kraus_operators: PyResult<Vec<SquareMatrix>> = kraus_operators
79 .into_iter()
80 .map(python_matrix_to_nalgebra_matrix)
81 .collect();
82 noisy_simulator::Operation::new(kraus_operators?)
83 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
84 .map(Self)
85 }
86
87 pub fn get_effect_matrix(&self) -> PythonMatrix {
88 nalgebra_matrix_to_python_matrix(self.0.effect_matrix())
89 }
90
91 pub fn get_operation_matrix(&self) -> PythonMatrix {
92 nalgebra_matrix_to_python_matrix(self.0.matrix())
93 }
94
95 pub fn get_kraus_operators(&self) -> Vec<PythonMatrix> {
96 let mut kraus_operators = Vec::new();
97 for kraus_operator in self.0.kraus_operators() {
98 kraus_operators.push(nalgebra_matrix_to_python_matrix(kraus_operator));
99 }
100 kraus_operators
101 }
102
103 pub fn get_number_of_qubits(&self) -> usize {
104 self.0.number_of_qubits()
105 }
106}
107
108#[pyclass]
109pub(crate) struct Instrument(noisy_simulator::Instrument);
110
111#[pymethods]
112impl Instrument {
113 #[new]
114 pub fn new(operations: Vec<Operation>) -> PyResult<Self> {
115 let operations = operations.into_iter().map(|op| op.0).collect();
116 noisy_simulator::Instrument::new(operations)
117 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
118 .map(Self)
119 }
120}
121
122#[pyclass]
123#[derive(Clone)]
124pub(crate) struct DensityMatrix {
125 /// Dimension of the matrix. E.g.: If the matrix is 5 x 5, then dimension is 5.
126 dimension: usize,
127 /// Number of qubits in the system.
128 number_of_qubits: usize,
129 /// Theoretical change in trace due to operations that have been applied so far.
130 trace_change: f64,
131 /// Vector storing the entries of the density matrix.
132 data: Vec<Complex<f64>>,
133}
134
135#[pymethods]
136impl DensityMatrix {
137 /// Returns a copy of the matrix data.
138 fn data(&self) -> Vec<Vec<Complex<f64>>> {
139 let mut density_matrix = Vec::with_capacity(self.dimension);
140 for row in 0..self.dimension {
141 let mut row_vec = Vec::with_capacity(self.dimension);
142 for col in 0..self.dimension {
143 row_vec.push(self.data[row * self.dimension + col]);
144 }
145 density_matrix.push(row_vec);
146 }
147 density_matrix
148 }
149
150 /// Returns the dimension of the matrix.
151 fn dimension(&self) -> usize {
152 self.dimension
153 }
154
155 /// Returns the number of qubits in the system.
156 fn number_of_qubits(&self) -> usize {
157 self.number_of_qubits
158 }
159}
160
161impl From<&noisy_simulator::DensityMatrix> for DensityMatrix {
162 fn from(dm: &noisy_simulator::DensityMatrix) -> Self {
163 Self {
164 dimension: dm.dimension(),
165 number_of_qubits: dm.number_of_qubits(),
166 trace_change: dm.trace_change(),
167 data: dm.data().iter().copied().collect::<Vec<_>>(),
168 }
169 }
170}
171
172impl TryInto<noisy_simulator::DensityMatrix> for DensityMatrix {
173 type Error = PyErr;
174
175 fn try_into(self) -> PyResult<noisy_simulator::DensityMatrix> {
176 noisy_simulator::DensityMatrix::try_from(
177 self.dimension,
178 self.number_of_qubits,
179 self.trace_change,
180 ComplexVector::from_vec(self.data),
181 )
182 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
183 }
184}
185
186#[pyclass]
187pub(crate) struct DensityMatrixSimulator(noisy_simulator::DensityMatrixSimulator);
188
189#[pymethods]
190impl DensityMatrixSimulator {
191 #[new]
192 #[pyo3(signature=(number_of_qubits, seed=None))]
193 pub fn new(number_of_qubits: usize, seed: Option<u64>) -> Self {
194 if let Some(seed) = seed {
195 Self(noisy_simulator::DensityMatrixSimulator::new_with_seed(
196 number_of_qubits,
197 seed,
198 ))
199 } else {
200 Self(noisy_simulator::DensityMatrixSimulator::new(
201 number_of_qubits,
202 ))
203 }
204 }
205
206 /// Apply an arbitrary operation to given qubit ids.
207 #[allow(clippy::needless_pass_by_value)]
208 pub fn apply_operation(&mut self, operation: &Operation, qubits: Vec<usize>) -> PyResult<()> {
209 self.0
210 .apply_operation(&operation.0, &qubits)
211 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
212 }
213
214 /// Apply non selective evolution.
215 #[allow(clippy::needless_pass_by_value)]
216 pub fn apply_instrument(
217 &mut self,
218 instrument: &Instrument,
219 qubits: Vec<usize>,
220 ) -> PyResult<()> {
221 self.0
222 .apply_instrument(&instrument.0, &qubits)
223 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
224 }
225
226 /// Performs selective evolution under the given instrument.
227 /// Returns the index of the observed outcome.
228 ///
229 /// Use this method to perform measurements on the quantum system.
230 #[allow(clippy::needless_pass_by_value)]
231 pub fn sample_instrument(
232 &mut self,
233 instrument: &Instrument,
234 qubits: Vec<usize>,
235 ) -> PyResult<usize> {
236 self.0
237 .sample_instrument(&instrument.0, &qubits)
238 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
239 }
240
241 /// Returns the `DensityMatrix` if the simulator is in a valid state.
242 pub fn get_state(&self) -> Option<DensityMatrix> {
243 match self.0.state() {
244 Ok(dm) => Some(dm.into()),
245 Err(_) => None,
246 }
247 }
248
249 /// Set state of the quantum system.
250 pub fn set_state(&mut self, state: DensityMatrix) -> PyResult<()> {
251 self.0
252 .set_state(state.try_into()?)
253 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
254 }
255
256 /// Set the trace of the quantum system.
257 pub fn set_trace(&mut self, trace: f64) -> PyResult<()> {
258 self.0
259 .set_trace(trace)
260 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
261 }
262}
263
264#[pyclass]
265#[derive(Clone)]
266pub(crate) struct StateVector {
267 /// Dimension of the matrix. E.g.: If the matrix is 5 x 5, then dimension is 5.
268 dimension: usize,
269 /// Number of qubits in the system.
270 number_of_qubits: usize,
271 /// Theoretical change in trace due to operations that have been applied so far.
272 trace_change: f64,
273 /// Vector storing the entries of the density matrix.
274 data: Vec<Complex<f64>>,
275}
276
277#[pymethods]
278impl StateVector {
279 /// Returns a copy of the matrix data.
280 fn data(&self) -> Vec<Complex<f64>> {
281 self.data.clone()
282 }
283
284 /// Returns the dimension of the matrix.
285 fn dimension(&self) -> usize {
286 self.dimension
287 }
288
289 /// Returns the number of qubits in the system.
290 fn number_of_qubits(&self) -> usize {
291 self.number_of_qubits
292 }
293}
294
295impl From<&noisy_simulator::StateVector> for StateVector {
296 fn from(dm: &noisy_simulator::StateVector) -> Self {
297 Self {
298 dimension: dm.dimension(),
299 number_of_qubits: dm.number_of_qubits(),
300 trace_change: dm.trace_change(),
301 data: dm.data().iter().copied().collect::<Vec<_>>(),
302 }
303 }
304}
305
306impl TryInto<noisy_simulator::StateVector> for StateVector {
307 type Error = PyErr;
308
309 fn try_into(self) -> PyResult<noisy_simulator::StateVector> {
310 noisy_simulator::StateVector::try_from(
311 self.dimension,
312 self.number_of_qubits,
313 self.trace_change,
314 ComplexVector::from_vec(self.data),
315 )
316 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
317 }
318}
319
320#[pyclass]
321pub(crate) struct StateVectorSimulator(noisy_simulator::StateVectorSimulator);
322
323#[pymethods]
324impl StateVectorSimulator {
325 #[new]
326 #[pyo3(signature=(number_of_qubits, seed=None))]
327 pub fn new(number_of_qubits: usize, seed: Option<u64>) -> Self {
328 if let Some(seed) = seed {
329 Self(noisy_simulator::StateVectorSimulator::new_with_seed(
330 number_of_qubits,
331 seed,
332 ))
333 } else {
334 Self(noisy_simulator::StateVectorSimulator::new(number_of_qubits))
335 }
336 }
337
338 /// Apply an arbitrary operation to given qubit ids.
339 #[allow(clippy::needless_pass_by_value)]
340 pub fn apply_operation(&mut self, operation: &Operation, qubits: Vec<usize>) -> PyResult<()> {
341 self.0
342 .apply_operation(&operation.0, &qubits)
343 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
344 }
345
346 /// Apply non selective evolution.
347 #[allow(clippy::needless_pass_by_value)]
348 pub fn apply_instrument(
349 &mut self,
350 instrument: &Instrument,
351 qubits: Vec<usize>,
352 ) -> PyResult<()> {
353 self.0
354 .apply_instrument(&instrument.0, &qubits)
355 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
356 }
357
358 /// Performs selective evolution under the given instrument.
359 /// Returns the index of the observed outcome.
360 ///
361 /// Use this method to perform measurements on the quantum system.
362 #[allow(clippy::needless_pass_by_value)]
363 pub fn sample_instrument(
364 &mut self,
365 instrument: &Instrument,
366 qubits: Vec<usize>,
367 ) -> PyResult<usize> {
368 self.0
369 .sample_instrument(&instrument.0, &qubits)
370 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
371 }
372
373 /// Returns the `StateVector` if the simulator is in a valid state.
374 pub fn get_state(&self) -> Option<StateVector> {
375 match self.0.state() {
376 Ok(dm) => Some(dm.into()),
377 Err(_) => None,
378 }
379 }
380
381 /// Set state of the quantum system.
382 pub fn set_state(&mut self, state: StateVector) -> PyResult<()> {
383 self.0
384 .set_state(state.try_into()?)
385 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
386 }
387
388 /// Set the trace of the quantum system.
389 pub fn set_trace(&mut self, trace: f64) -> PyResult<()> {
390 self.0
391 .set_trace(trace)
392 .map_err(|e| NoisySimulatorError::new_err(e.to_string()))
393 }
394}
395