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::random::prelude::*;
13use scirs2_core::Complex64;
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 const 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 const 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        if let Some(outcome) = self.outcome {
145            // Project onto specific outcome
146            let (prob, new_state) = self.project_onto_outcome(state, outcome)?;
147            Ok(OperationResult::Probabilistic {
148                outcome,
149                probability: prob,
150                state: new_state,
151            })
152        } else {
153            // Calculate all outcomes
154            let probabilities = self.get_probabilities(state)?;
155            let mut outcomes = Vec::new();
156
157            for (outcome, &prob) in probabilities.iter().enumerate() {
158                if prob > 1e-10 {
159                    let (_, new_state) = self.project_onto_outcome(state, outcome)?;
160                    outcomes.push(MeasurementOutcome {
161                        outcome,
162                        probability: prob,
163                        state: new_state,
164                    });
165                }
166            }
167
168            Ok(OperationResult::MultiOutcome(outcomes))
169        }
170    }
171
172    fn apply_to_density_matrix(
173        &self,
174        rho: &ArrayView2<Complex64>,
175    ) -> QuantRS2Result<Array2<Complex64>> {
176        let n_qubits = (rho.nrows() as f64).log2() as usize;
177        let mut result = Array2::zeros(rho.raw_dim());
178
179        // Apply measurement operators: ρ' = Σ_i P_i ρ P_i
180        for outcome in 0..(1 << self.qubits.len()) {
181            let projector = self.create_projector(outcome, n_qubits)?;
182            let proj_rho = projector.dot(rho).dot(&projector);
183            result = result + proj_rho;
184        }
185
186        Ok(result)
187    }
188
189    fn qubits(&self) -> Vec<QubitId> {
190        self.qubits.clone()
191    }
192
193    fn is_deterministic(&self) -> bool {
194        false
195    }
196}
197
198impl ProjectiveMeasurement {
199    /// Create projector matrix for a specific outcome
200    fn create_projector(
201        &self,
202        outcome: usize,
203        total_qubits: usize,
204    ) -> QuantRS2Result<Array2<Complex64>> {
205        let dim = 1 << total_qubits;
206        let mut projector = Array2::zeros((dim, dim));
207
208        for idx in 0..dim {
209            if self.extract_outcome_from_index(idx, total_qubits) == outcome {
210                projector[[idx, idx]] = Complex64::new(1.0, 0.0);
211            }
212        }
213
214        Ok(projector)
215    }
216}
217
218/// POVM (Positive Operator-Valued Measure) measurement
219#[derive(Debug, Clone)]
220pub struct POVMMeasurement {
221    /// The POVM elements (must sum to identity)
222    pub elements: Vec<Array2<Complex64>>,
223    /// Qubits this POVM acts on
224    pub qubits: Vec<QubitId>,
225}
226
227impl POVMMeasurement {
228    /// Create a new POVM measurement
229    pub fn new(elements: Vec<Array2<Complex64>>, qubits: Vec<QubitId>) -> QuantRS2Result<Self> {
230        // Verify that elements sum to identity
231        let dim = elements[0].nrows();
232        let mut sum = Array2::<Complex64>::zeros((dim, dim));
233
234        for element in &elements {
235            if element.nrows() != dim || element.ncols() != dim {
236                return Err(QuantRS2Error::InvalidInput(
237                    "All POVM elements must have the same dimension".to_string(),
238                ));
239            }
240            sum = sum + element;
241        }
242
243        // Check if sum is identity (within tolerance)
244        for i in 0..dim {
245            for j in 0..dim {
246                let expected = if i == j {
247                    Complex64::new(1.0, 0.0)
248                } else {
249                    Complex64::new(0.0, 0.0)
250                };
251                let diff: Complex64 = sum[[i, j]] - expected;
252                if diff.norm_sqr().sqrt() > 1e-10 {
253                    return Err(QuantRS2Error::InvalidInput(
254                        "POVM elements do not sum to identity".to_string(),
255                    ));
256                }
257            }
258        }
259
260        Ok(Self { elements, qubits })
261    }
262
263    /// Get measurement probabilities
264    pub fn get_probabilities(&self, state: &ArrayView1<Complex64>) -> QuantRS2Result<Vec<f64>> {
265        let mut probabilities = Vec::new();
266
267        for element in &self.elements {
268            // Probability = <ψ|M_i|ψ>
269            let temp = element.dot(state);
270            let prob = state
271                .iter()
272                .zip(temp.iter())
273                .map(|(&psi, &m_psi)| psi.conj() * m_psi)
274                .sum::<Complex64>()
275                .re;
276            probabilities.push(prob);
277        }
278
279        Ok(probabilities)
280    }
281}
282
283impl QuantumOperation for POVMMeasurement {
284    fn apply_to_state(&self, state: &ArrayView1<Complex64>) -> QuantRS2Result<OperationResult> {
285        let probabilities = self.get_probabilities(state)?;
286        let mut outcomes = Vec::new();
287
288        for (i, (&prob, element)) in probabilities.iter().zip(&self.elements).enumerate() {
289            if prob > 1e-10 {
290                // Apply measurement operator: |ψ'> = M_i|ψ>/√p_i
291                let new_state = element.dot(state) / prob.sqrt();
292                outcomes.push(MeasurementOutcome {
293                    outcome: i,
294                    probability: prob,
295                    state: new_state,
296                });
297            }
298        }
299
300        Ok(OperationResult::MultiOutcome(outcomes))
301    }
302
303    fn apply_to_density_matrix(
304        &self,
305        rho: &ArrayView2<Complex64>,
306    ) -> QuantRS2Result<Array2<Complex64>> {
307        let mut result = Array2::zeros(rho.raw_dim());
308
309        // Apply POVM: ρ' = Σ_i M_i ρ M_i†
310        for element in &self.elements {
311            let m_dag = element.t().mapv(|x| x.conj());
312            let term = element.dot(rho).dot(&m_dag);
313            result = result + term;
314        }
315
316        Ok(result)
317    }
318
319    fn qubits(&self) -> Vec<QubitId> {
320        self.qubits.clone()
321    }
322
323    fn is_deterministic(&self) -> bool {
324        false
325    }
326}
327
328/// Reset operation that sets qubits to |0⟩
329#[derive(Debug, Clone)]
330pub struct Reset {
331    /// Qubits to reset
332    pub qubits: Vec<QubitId>,
333}
334
335impl Reset {
336    /// Create a new reset operation
337    pub const fn new(qubits: Vec<QubitId>) -> Self {
338        Self { qubits }
339    }
340}
341
342impl QuantumOperation for Reset {
343    fn apply_to_state(&self, state: &ArrayView1<Complex64>) -> QuantRS2Result<OperationResult> {
344        let n_qubits = (state.len() as f64).log2() as usize;
345        let mut new_state = Array1::zeros(state.len());
346
347        // Project onto |0⟩ for specified qubits and normalize
348        let mut norm_squared = 0.0;
349        for (idx, &amp) in state.iter().enumerate() {
350            let mut should_keep = true;
351            for &qubit in &self.qubits {
352                let bit = (idx >> (n_qubits - 1 - qubit.0 as usize)) & 1;
353                if bit != 0 {
354                    should_keep = false;
355                    break;
356                }
357            }
358            if should_keep {
359                new_state[idx] = amp;
360                norm_squared += amp.norm_sqr();
361            }
362        }
363
364        if norm_squared < 1e-10 {
365            // If projection gives zero, just set to |0...0⟩
366            new_state[0] = Complex64::new(1.0, 0.0);
367        } else {
368            new_state = new_state / norm_squared.sqrt();
369        }
370
371        Ok(OperationResult::Deterministic(new_state))
372    }
373
374    fn apply_to_density_matrix(
375        &self,
376        rho: &ArrayView2<Complex64>,
377    ) -> QuantRS2Result<Array2<Complex64>> {
378        // For reset, we trace out the qubits and replace with |0⟩⟨0|
379        // This is a simplified implementation
380        let n_qubits = (rho.nrows() as f64).log2() as usize;
381        let mut result = Array2::zeros(rho.raw_dim());
382
383        // Project onto |0⟩ for reset qubits
384        for i in 0..rho.nrows() {
385            for j in 0..rho.ncols() {
386                let mut should_keep = true;
387                for &qubit in &self.qubits {
388                    let bit_i = (i >> (n_qubits - 1 - qubit.0 as usize)) & 1;
389                    let bit_j = (j >> (n_qubits - 1 - qubit.0 as usize)) & 1;
390                    if bit_i != 0 || bit_j != 0 {
391                        should_keep = false;
392                        break;
393                    }
394                }
395                if should_keep {
396                    result[[i, j]] = rho[[i, j]];
397                }
398            }
399        }
400
401        // Renormalize
402        let trace = (0..result.nrows()).map(|i| result[[i, i]].re).sum::<f64>();
403        if trace > 1e-10 {
404            result = result / trace;
405        }
406
407        Ok(result)
408    }
409
410    fn qubits(&self) -> Vec<QubitId> {
411        self.qubits.clone()
412    }
413
414    fn is_deterministic(&self) -> bool {
415        true
416    }
417}
418
419/// Sample from measurement outcomes according to their probabilities
420pub fn sample_outcome(probabilities: &[f64]) -> QuantRS2Result<usize> {
421    let mut rng = thread_rng();
422    let r: f64 = rng.gen();
423
424    let mut cumsum = 0.0;
425    for (i, &prob) in probabilities.iter().enumerate() {
426        cumsum += prob;
427        if r < cumsum {
428            return Ok(i);
429        }
430    }
431
432    // Should not reach here if probabilities sum to 1
433    Err(QuantRS2Error::ComputationError(
434        "Probabilities do not sum to 1".to_string(),
435    ))
436}
437
438/// Apply a quantum operation and sample an outcome
439pub fn apply_and_sample<O: QuantumOperation>(
440    operation: &O,
441    state: &ArrayView1<Complex64>,
442) -> QuantRS2Result<(usize, Array1<Complex64>)> {
443    match operation.apply_to_state(state)? {
444        OperationResult::Deterministic(new_state) => Ok((0, new_state)),
445        OperationResult::Probabilistic { outcome, state, .. } => Ok((outcome, state)),
446        OperationResult::MultiOutcome(outcomes) => {
447            let probabilities: Vec<f64> = outcomes.iter().map(|o| o.probability).collect();
448            let sampled_idx = sample_outcome(&probabilities)?;
449            let outcome = &outcomes[sampled_idx];
450            Ok((outcome.outcome, outcome.state.clone()))
451        }
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458
459    #[test]
460    fn test_projective_measurement() {
461        // |+⟩ state
462        let state = Array1::from_vec(vec![
463            Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
464            Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
465        ]);
466
467        let measurement = ProjectiveMeasurement::new(vec![QubitId(0)]);
468        let probs = measurement
469            .get_probabilities(&state.view())
470            .expect("Failed to compute measurement probabilities");
471
472        assert!((probs[0] - 0.5).abs() < 1e-10);
473        assert!((probs[1] - 0.5).abs() < 1e-10);
474    }
475
476    #[test]
477    fn test_reset_operation() {
478        // |1⟩ state
479        let state = Array1::from_vec(vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)]);
480
481        let reset = Reset::new(vec![QubitId(0)]);
482        let result = reset
483            .apply_to_state(&state.view())
484            .expect("Failed to apply reset operation to state");
485
486        match result {
487            OperationResult::Deterministic(new_state) => {
488                assert!((new_state[0].norm() - 1.0).abs() < 1e-10);
489                assert!(new_state[1].norm() < 1e-10);
490            }
491            _ => panic!("Reset should be deterministic"),
492        }
493    }
494}