quantrs2_core/
operations.rs

1//! Non-unitary quantum operations and measurements
2//!
3//! This module provides support for non-unitary quantum operations including:
4//! - Projective measurements
5//! - POVM measurements
6//! - Quantum channels
7//! - Reset operations
8
9use crate::error::{QuantRS2Error, QuantRS2Result};
10use crate::matrix_ops::DenseMatrix;
11use crate::qubit::QubitId;
12use ndarray::{Array1, Array2, ArrayView1, ArrayView2};
13use num_complex::Complex64;
14use rand::prelude::*;
15use rand::thread_rng;
16use std::fmt::Debug;
17
18/// Trait for quantum operations (both unitary and non-unitary)
19pub trait QuantumOperation: Debug + Send + Sync {
20    /// Apply the operation to a state vector
21    fn apply_to_state(&self, state: &ArrayView1<Complex64>) -> QuantRS2Result<OperationResult>;
22
23    /// Apply the operation to a density matrix
24    fn apply_to_density_matrix(
25        &self,
26        rho: &ArrayView2<Complex64>,
27    ) -> QuantRS2Result<Array2<Complex64>>;
28
29    /// Get the qubits this operation acts on
30    fn qubits(&self) -> Vec<QubitId>;
31
32    /// Check if the operation is deterministic
33    fn is_deterministic(&self) -> bool;
34}
35
36/// Result of a quantum operation
37#[derive(Debug, Clone)]
38pub enum OperationResult {
39    /// Deterministic result (new state)
40    Deterministic(Array1<Complex64>),
41    /// Probabilistic result (outcome, probability, new state)
42    Probabilistic {
43        outcome: usize,
44        probability: f64,
45        state: Array1<Complex64>,
46    },
47    /// Multiple possible outcomes
48    MultiOutcome(Vec<MeasurementOutcome>),
49}
50
51/// A single measurement outcome
52#[derive(Debug, Clone)]
53pub struct MeasurementOutcome {
54    /// The measurement result
55    pub outcome: usize,
56    /// The probability of this outcome
57    pub probability: f64,
58    /// The post-measurement state
59    pub state: Array1<Complex64>,
60}
61
62/// Projective measurement in the computational basis
63#[derive(Debug, Clone)]
64pub struct ProjectiveMeasurement {
65    /// Qubits to measure
66    pub qubits: Vec<QubitId>,
67    /// Optional specific outcome to project onto
68    pub outcome: Option<usize>,
69}
70
71impl ProjectiveMeasurement {
72    /// Create a new projective measurement
73    pub fn new(qubits: Vec<QubitId>) -> Self {
74        Self {
75            qubits,
76            outcome: None,
77        }
78    }
79
80    /// Create a measurement that projects onto a specific outcome
81    pub fn with_outcome(qubits: Vec<QubitId>, outcome: usize) -> Self {
82        Self {
83            qubits,
84            outcome: Some(outcome),
85        }
86    }
87
88    /// Calculate measurement probabilities
89    pub fn get_probabilities(&self, state: &ArrayView1<Complex64>) -> QuantRS2Result<Vec<f64>> {
90        let n_qubits = (state.len() as f64).log2() as usize;
91        let n_outcomes = 1 << self.qubits.len();
92        let mut probabilities = vec![0.0; n_outcomes];
93
94        // Calculate probability for each outcome
95        for (idx, amp) in state.iter().enumerate() {
96            let outcome = self.extract_outcome_from_index(idx, n_qubits);
97            probabilities[outcome] += amp.norm_sqr();
98        }
99
100        Ok(probabilities)
101    }
102
103    /// Extract the measurement outcome from a basis state index
104    fn extract_outcome_from_index(&self, index: usize, total_qubits: usize) -> usize {
105        let mut outcome = 0;
106        for (i, &qubit) in self.qubits.iter().enumerate() {
107            let bit = (index >> (total_qubits - 1 - qubit.0 as usize)) & 1;
108            outcome |= bit << (self.qubits.len() - 1 - i);
109        }
110        outcome
111    }
112
113    /// Project state onto a specific outcome
114    fn project_onto_outcome(
115        &self,
116        state: &ArrayView1<Complex64>,
117        outcome: usize,
118    ) -> QuantRS2Result<(f64, Array1<Complex64>)> {
119        let n_qubits = (state.len() as f64).log2() as usize;
120        let mut projected = Array1::zeros(state.len());
121        let mut norm_squared = 0.0;
122
123        for (idx, &amp) in state.iter().enumerate() {
124            if self.extract_outcome_from_index(idx, n_qubits) == outcome {
125                projected[idx] = amp;
126                norm_squared += amp.norm_sqr();
127            }
128        }
129
130        if norm_squared < 1e-10 {
131            return Err(QuantRS2Error::InvalidInput(
132                "Measurement outcome has zero probability".to_string(),
133            ));
134        }
135
136        // Normalize the projected state
137        let norm = norm_squared.sqrt();
138        projected = projected / norm;
139
140        Ok((norm_squared, projected))
141    }
142}
143
144impl QuantumOperation for ProjectiveMeasurement {
145    fn apply_to_state(&self, state: &ArrayView1<Complex64>) -> QuantRS2Result<OperationResult> {
146        match self.outcome {
147            Some(outcome) => {
148                // Project onto specific outcome
149                let (prob, new_state) = self.project_onto_outcome(state, outcome)?;
150                Ok(OperationResult::Probabilistic {
151                    outcome,
152                    probability: prob,
153                    state: new_state,
154                })
155            }
156            None => {
157                // Calculate all outcomes
158                let probabilities = self.get_probabilities(state)?;
159                let mut outcomes = Vec::new();
160
161                for (outcome, &prob) in probabilities.iter().enumerate() {
162                    if prob > 1e-10 {
163                        let (_, new_state) = self.project_onto_outcome(state, outcome)?;
164                        outcomes.push(MeasurementOutcome {
165                            outcome,
166                            probability: prob,
167                            state: new_state,
168                        });
169                    }
170                }
171
172                Ok(OperationResult::MultiOutcome(outcomes))
173            }
174        }
175    }
176
177    fn apply_to_density_matrix(
178        &self,
179        rho: &ArrayView2<Complex64>,
180    ) -> QuantRS2Result<Array2<Complex64>> {
181        let n_qubits = (rho.nrows() as f64).log2() as usize;
182        let mut result = Array2::zeros(rho.raw_dim());
183
184        // Apply measurement operators: ρ' = Σ_i P_i ρ P_i
185        for outcome in 0..(1 << self.qubits.len()) {
186            let projector = self.create_projector(outcome, n_qubits)?;
187            let proj_rho = projector.dot(rho).dot(&projector);
188            result = result + proj_rho;
189        }
190
191        Ok(result)
192    }
193
194    fn qubits(&self) -> Vec<QubitId> {
195        self.qubits.clone()
196    }
197
198    fn is_deterministic(&self) -> bool {
199        false
200    }
201}
202
203impl ProjectiveMeasurement {
204    /// Create projector matrix for a specific outcome
205    fn create_projector(
206        &self,
207        outcome: usize,
208        total_qubits: usize,
209    ) -> QuantRS2Result<Array2<Complex64>> {
210        let dim = 1 << total_qubits;
211        let mut projector = Array2::zeros((dim, dim));
212
213        for idx in 0..dim {
214            if self.extract_outcome_from_index(idx, total_qubits) == outcome {
215                projector[[idx, idx]] = Complex64::new(1.0, 0.0);
216            }
217        }
218
219        Ok(projector)
220    }
221}
222
223/// POVM (Positive Operator-Valued Measure) measurement
224#[derive(Debug, Clone)]
225pub struct POVMMeasurement {
226    /// The POVM elements (must sum to identity)
227    pub elements: Vec<Array2<Complex64>>,
228    /// Qubits this POVM acts on
229    pub qubits: Vec<QubitId>,
230}
231
232impl POVMMeasurement {
233    /// Create a new POVM measurement
234    pub fn new(elements: Vec<Array2<Complex64>>, qubits: Vec<QubitId>) -> QuantRS2Result<Self> {
235        // Verify that elements sum to identity
236        let dim = elements[0].nrows();
237        let mut sum = Array2::<Complex64>::zeros((dim, dim));
238
239        for element in &elements {
240            if element.nrows() != dim || element.ncols() != dim {
241                return Err(QuantRS2Error::InvalidInput(
242                    "All POVM elements must have the same dimension".to_string(),
243                ));
244            }
245            sum = sum + element;
246        }
247
248        // Check if sum is identity (within tolerance)
249        for i in 0..dim {
250            for j in 0..dim {
251                let expected = if i == j {
252                    Complex64::new(1.0, 0.0)
253                } else {
254                    Complex64::new(0.0, 0.0)
255                };
256                let diff: Complex64 = sum[[i, j]] - expected;
257                if diff.norm_sqr().sqrt() > 1e-10 {
258                    return Err(QuantRS2Error::InvalidInput(
259                        "POVM elements do not sum to identity".to_string(),
260                    ));
261                }
262            }
263        }
264
265        Ok(Self { elements, qubits })
266    }
267
268    /// Get measurement probabilities
269    pub fn get_probabilities(&self, state: &ArrayView1<Complex64>) -> QuantRS2Result<Vec<f64>> {
270        let mut probabilities = Vec::new();
271
272        for element in &self.elements {
273            // Probability = <ψ|M_i|ψ>
274            let temp = element.dot(state);
275            let prob = state
276                .iter()
277                .zip(temp.iter())
278                .map(|(&psi, &m_psi)| psi.conj() * m_psi)
279                .sum::<Complex64>()
280                .re;
281            probabilities.push(prob);
282        }
283
284        Ok(probabilities)
285    }
286}
287
288impl QuantumOperation for POVMMeasurement {
289    fn apply_to_state(&self, state: &ArrayView1<Complex64>) -> QuantRS2Result<OperationResult> {
290        let probabilities = self.get_probabilities(state)?;
291        let mut outcomes = Vec::new();
292
293        for (i, (&prob, element)) in probabilities.iter().zip(&self.elements).enumerate() {
294            if prob > 1e-10 {
295                // Apply measurement operator: |ψ'> = M_i|ψ>/√p_i
296                let new_state = element.dot(state) / prob.sqrt();
297                outcomes.push(MeasurementOutcome {
298                    outcome: i,
299                    probability: prob,
300                    state: new_state,
301                });
302            }
303        }
304
305        Ok(OperationResult::MultiOutcome(outcomes))
306    }
307
308    fn apply_to_density_matrix(
309        &self,
310        rho: &ArrayView2<Complex64>,
311    ) -> QuantRS2Result<Array2<Complex64>> {
312        let mut result = Array2::zeros(rho.raw_dim());
313
314        // Apply POVM: ρ' = Σ_i M_i ρ M_i†
315        for element in &self.elements {
316            let m_dag = element.t().mapv(|x| x.conj());
317            let term = element.dot(rho).dot(&m_dag);
318            result = result + term;
319        }
320
321        Ok(result)
322    }
323
324    fn qubits(&self) -> Vec<QubitId> {
325        self.qubits.clone()
326    }
327
328    fn is_deterministic(&self) -> bool {
329        false
330    }
331}
332
333/// Reset operation that sets qubits to |0⟩
334#[derive(Debug, Clone)]
335pub struct Reset {
336    /// Qubits to reset
337    pub qubits: Vec<QubitId>,
338}
339
340impl Reset {
341    /// Create a new reset operation
342    pub fn new(qubits: Vec<QubitId>) -> Self {
343        Self { qubits }
344    }
345}
346
347impl QuantumOperation for Reset {
348    fn apply_to_state(&self, state: &ArrayView1<Complex64>) -> QuantRS2Result<OperationResult> {
349        let n_qubits = (state.len() as f64).log2() as usize;
350        let mut new_state = Array1::zeros(state.len());
351
352        // Project onto |0⟩ for specified qubits and normalize
353        let mut norm_squared = 0.0;
354        for (idx, &amp) in state.iter().enumerate() {
355            let mut should_keep = true;
356            for &qubit in &self.qubits {
357                let bit = (idx >> (n_qubits - 1 - qubit.0 as usize)) & 1;
358                if bit != 0 {
359                    should_keep = false;
360                    break;
361                }
362            }
363            if should_keep {
364                new_state[idx] = amp;
365                norm_squared += amp.norm_sqr();
366            }
367        }
368
369        if norm_squared < 1e-10 {
370            // If projection gives zero, just set to |0...0⟩
371            new_state[0] = Complex64::new(1.0, 0.0);
372        } else {
373            new_state = new_state / norm_squared.sqrt();
374        }
375
376        Ok(OperationResult::Deterministic(new_state))
377    }
378
379    fn apply_to_density_matrix(
380        &self,
381        rho: &ArrayView2<Complex64>,
382    ) -> QuantRS2Result<Array2<Complex64>> {
383        // For reset, we trace out the qubits and replace with |0⟩⟨0|
384        // This is a simplified implementation
385        let n_qubits = (rho.nrows() as f64).log2() as usize;
386        let mut result = Array2::zeros(rho.raw_dim());
387
388        // Project onto |0⟩ for reset qubits
389        for i in 0..rho.nrows() {
390            for j in 0..rho.ncols() {
391                let mut should_keep = true;
392                for &qubit in &self.qubits {
393                    let bit_i = (i >> (n_qubits - 1 - qubit.0 as usize)) & 1;
394                    let bit_j = (j >> (n_qubits - 1 - qubit.0 as usize)) & 1;
395                    if bit_i != 0 || bit_j != 0 {
396                        should_keep = false;
397                        break;
398                    }
399                }
400                if should_keep {
401                    result[[i, j]] = rho[[i, j]];
402                }
403            }
404        }
405
406        // Renormalize
407        let trace = (0..result.nrows()).map(|i| result[[i, i]].re).sum::<f64>();
408        if trace > 1e-10 {
409            result = result / trace;
410        }
411
412        Ok(result)
413    }
414
415    fn qubits(&self) -> Vec<QubitId> {
416        self.qubits.clone()
417    }
418
419    fn is_deterministic(&self) -> bool {
420        true
421    }
422}
423
424/// Sample from measurement outcomes according to their probabilities
425pub fn sample_outcome(probabilities: &[f64]) -> QuantRS2Result<usize> {
426    let mut rng = thread_rng();
427    let r: f64 = rng.gen();
428
429    let mut cumsum = 0.0;
430    for (i, &prob) in probabilities.iter().enumerate() {
431        cumsum += prob;
432        if r < cumsum {
433            return Ok(i);
434        }
435    }
436
437    // Should not reach here if probabilities sum to 1
438    Err(QuantRS2Error::ComputationError(
439        "Probabilities do not sum to 1".to_string(),
440    ))
441}
442
443/// Apply a quantum operation and sample an outcome
444pub fn apply_and_sample<O: QuantumOperation>(
445    operation: &O,
446    state: &ArrayView1<Complex64>,
447) -> QuantRS2Result<(usize, Array1<Complex64>)> {
448    match operation.apply_to_state(state)? {
449        OperationResult::Deterministic(new_state) => Ok((0, new_state)),
450        OperationResult::Probabilistic { outcome, state, .. } => Ok((outcome, state)),
451        OperationResult::MultiOutcome(outcomes) => {
452            let probabilities: Vec<f64> = outcomes.iter().map(|o| o.probability).collect();
453            let sampled_idx = sample_outcome(&probabilities)?;
454            let outcome = &outcomes[sampled_idx];
455            Ok((outcome.outcome, outcome.state.clone()))
456        }
457    }
458}
459
460#[cfg(test)]
461mod tests {
462    use super::*;
463
464    #[test]
465    fn test_projective_measurement() {
466        // |+⟩ state
467        let state = Array1::from_vec(vec![
468            Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
469            Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
470        ]);
471
472        let measurement = ProjectiveMeasurement::new(vec![QubitId(0)]);
473        let probs = measurement.get_probabilities(&state.view()).unwrap();
474
475        assert!((probs[0] - 0.5).abs() < 1e-10);
476        assert!((probs[1] - 0.5).abs() < 1e-10);
477    }
478
479    #[test]
480    fn test_reset_operation() {
481        // |1⟩ state
482        let state = Array1::from_vec(vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)]);
483
484        let reset = Reset::new(vec![QubitId(0)]);
485        let result = reset.apply_to_state(&state.view()).unwrap();
486
487        match result {
488            OperationResult::Deterministic(new_state) => {
489                assert!((new_state[0].norm() - 1.0).abs() < 1e-10);
490                assert!(new_state[1].norm() < 1e-10);
491            }
492            _ => panic!("Reset should be deterministic"),
493        }
494    }
495}