quantum_sim/
random_gate.rs

1//! # Random Operator Generator
2//!
3//! This module provides tools to generate random **unitary matrices**,
4//! **density matrices**, and **state vectors**....
5//! It supports different distributions (Haar, Bures) and both real and complex
6//! cases. The API follows a *builder pattern* for flexible, keyword-like usage.
7//!
8//! # Example
9//! ```
10//! use quantum_sim::random_gate::{RandomDensityMat, RandomUnitary};
11//! use num_complex::Complex;
12//!
13//! let u = RandomUnitary::<f64>::new(4).is_real(false).build();
14//! let rho = RandomDensityMat::<f64>::new(4).build();
15//! let rho_bures = RandomDensityMat::<f64>::new(4).bures().is_real(true).build();
16//! ```
17//!
18//! ## Features
19//! - Haar and Bures random state generation
20//! - Real or complex support
21//! - Generic over f32/f64
22//! - Type-safe and builder-based interface
23
24use ndarray::{Array1, Array2};
25use ndarray_linalg::{Lapack, QR};
26use ndarray_rand::{
27    RandomExt,
28    rand::distributions::Distribution,
29    rand::thread_rng,
30    rand_distr::{Normal, StandardNormal},
31};
32use num_complex::Complex;
33use num_traits::Float;
34
35/// Internal: generate a random Ginibre matrix.
36///
37/// # Parameters
38/// - `m`: number of columns
39/// - `n`: number of rows
40/// - `is_real`: whether to generate real entries
41///
42/// # Returns
43/// Random matrix sampled from real or complex Gaussian distribution.
44fn ginibre<T>(m: usize, n: usize, is_real: bool) -> Array2<Complex<T>>
45where
46    T: Float + ndarray_linalg::Scalar,
47    StandardNormal: Distribution<T>,
48{
49    let mut rng = thread_rng();
50    let normal = Normal::new(T::zero(), T::one()).unwrap();
51
52    if is_real {
53        Array2::random_using((m, n), normal, &mut rng).mapv(|x| Complex::new(x, T::zero()))
54    } else {
55        let re = Array2::random_using((m, n), normal, &mut rng);
56        let im = Array2::random_using((m, n), normal, &mut rng);
57        re.mapv(|x| Complex::new(x, T::zero())) + im.mapv(|y| Complex::new(T::zero(), y))
58    }
59}
60
61/// A struct for generating random unitary matrices.
62///
63/// This struct creates a random matrix distributed according to the Haar measure,
64/// with complex case and an orthogonal matrix in the real case.
65///
66/// # Example
67/// ```rust
68/// use quantum_sim::random_gate::RandomUnitary;
69///
70/// let ru = RandomUnitary::<f64>::new(4).build();
71/// let ru1 = RandomUnitary::<f64>::new(4).is_real(true).build();
72/// ```
73pub struct RandomUnitary<T> {
74    dim_col: usize,
75    dim_row: Option<usize>,
76    is_real: Option<bool>,
77    _marker: std::marker::PhantomData<T>,
78}
79
80impl<T> RandomUnitary<T>
81where
82    T: Float + ndarray_linalg::Scalar,
83    StandardNormal: Distribution<T>,
84    Complex<T>: Lapack,
85{
86    /// Creates a new `RandomUnitary` builder.
87    ///
88    /// # Arguments
89    ///
90    /// * `dim_col` - The number of columns for the matrix. By default, the
91    ///   matrix will be square (`dim_row` = `dim_col`).
92    pub fn new(dim_col: usize) -> Self {
93        Self {
94            dim_col,
95            dim_row: None,
96            is_real: None,
97            _marker: std::marker::PhantomData,
98        }
99    }
100
101    /// Set row dimension (non-square matrices).
102    pub fn dim(mut self, dim_row: usize) -> Self {
103        self.dim_row = Some(dim_row);
104        self
105    }
106
107    /// Set to real matrix.
108    pub fn is_real(mut self, flag: bool) -> Self {
109        self.is_real = Some(flag);
110        self
111    }
112
113    /// Builds the random unitary (or orthogonal) matrix.
114    ///
115    /// # Returns
116    ///
117    /// An `Array2<Complex<T>>` representing the random matrix.
118    pub fn build(self) -> Array2<Complex<T>> {
119        let dim_row = self.dim_row.unwrap_or(self.dim_col);
120        let is_real = self.is_real.unwrap_or(false);
121        let gin: Array2<Complex<T>> = ginibre::<T>(self.dim_col, dim_row, is_real);
122        let (mut q, r) = gin.qr().expect("QR decomposition failed");
123
124        let r_diag_signs: Array1<T> = r.diag().mapv(|c| {
125            let re = c.re.signum();
126            if re == T::zero() { T::one() } else { re }
127        });
128
129        for (mut col, &sign) in q.columns_mut().into_iter().zip(&r_diag_signs) {
130            col.mapv_inplace(|z| z * Complex::new(sign, T::zero()));
131        }
132
133        q
134    }
135}
136
137/// A struct for generating random density matrices.
138///
139/// It can generate random density matrices from different statistical ensembles
140/// (Haar or Bures), both real and complex matrices.
141///
142///  # Example
143/// ```rust
144/// use quantum_sim::random_gate::{RandomDensityMat, RandomUnitary};
145///
146/// let rho = RandomDensityMat::<f64>::new(4).build();
147/// let rho_bures = RandomDensityMat::<f64>::new(4).bures().is_real(true).build();
148/// ```
149
150pub struct RandomDensityMat<T: Float + ndarray_linalg::Scalar> {
151    dim_col: usize,
152    dim_row: Option<usize>,
153    is_real: Option<bool>,
154    is_bures: bool,
155    _marker: std::marker::PhantomData<T>,
156}
157
158impl<T> RandomDensityMat<T>
159where
160    T: Float + ndarray_linalg::Scalar,
161    StandardNormal: Distribution<T>,
162    Complex<T>: Lapack,
163{
164    pub fn new(dim_col: usize) -> Self {
165        Self {
166            dim_col,
167            dim_row: None,
168            is_real: None,
169            is_bures: false,
170            _marker: std::marker::PhantomData,
171        }
172    }
173
174    /// Set row dimension (non-square).
175    pub fn dim(mut self, dim_row: usize) -> Self {
176        self.dim_row = Some(dim_row);
177        self
178    }
179
180    /// Set to real density matrix.
181    pub fn is_real(mut self, flag: bool) -> Self {
182        self.is_real = Some(flag);
183        self
184    }
185
186    /// Use Bures distribution instead of Haar.
187    pub fn bures(mut self) -> Self {
188        self.is_bures = true;
189        self
190    }
191
192    /// Build and return a normalized density matrix.
193    ///
194    /// # Returns
195    /// An `Array2<Complex<T>>` representing the random, trace-normalized density matrix.
196    pub fn build(self) -> Array2<Complex<T>> {
197        let dim_row = self.dim_row.unwrap_or(self.dim_col);
198        let is_real = self.is_real.unwrap_or(false);
199
200        let mut gin = ginibre::<T>(self.dim_col, dim_row, is_real);
201
202        // Bures case: (U + I)G
203        if self.is_bures {
204            let u = RandomUnitary::<T>::new(self.dim_col)
205                .is_real(is_real)
206                .build();
207            let eye = Array2::<Complex<T>>::eye(self.dim_col);
208            gin = (u + eye).dot(&gin);
209        }
210
211        // ρ = GG† / Tr(GG†)
212        let mut rho = gin.dot(&gin.t().mapv(|c| c.conj()));
213        let trace = rho.diag().sum();
214        rho.mapv_inplace(|x| x / trace);
215        rho
216    }
217}