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}