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