1use std::f64::consts::PI;
2use super::schrodinger::{Complex, WaveFunction1D, dft, idft};
3
4pub struct WaveFunctionRenderer1D {
6 pub width: usize,
7 pub brightness_scale: f64,
8}
9
10impl WaveFunctionRenderer1D {
11 pub fn new(width: usize) -> Self {
12 Self { width, brightness_scale: 1.0 }
13 }
14
15 pub fn render(&self, wf: &WaveFunction1D) -> Vec<(char, f64, f64, f64, f64)> {
17 let n = wf.n();
18 let mut result = Vec::with_capacity(self.width);
19 for i in 0..self.width {
20 let idx = (i * n) / self.width.max(1);
21 let idx = idx.min(n - 1);
22 let c = wf.psi[idx];
23 let prob = c.norm_sq() * self.brightness_scale;
24 let phase = c.arg();
25 let (r, g, b) = PhaseColorMap::phase_to_rgb(phase);
26 let ch = brightness_to_char(prob);
27 result.push((ch, r, g, b, prob));
28 }
29 result
30 }
31}
32
33pub struct WaveFunctionRenderer2D {
35 pub width: usize,
36 pub height: usize,
37 pub brightness_scale: f64,
38}
39
40impl WaveFunctionRenderer2D {
41 pub fn new(width: usize, height: usize) -> Self {
42 Self { width, height, brightness_scale: 1.0 }
43 }
44
45 pub fn render(&self, psi: &[Vec<Complex>]) -> Vec<Vec<(char, f64, f64, f64, f64)>> {
46 let nx = psi.len();
47 let ny = if nx > 0 { psi[0].len() } else { 0 };
48 let mut grid = Vec::with_capacity(self.height);
49 for j in 0..self.height {
50 let mut row = Vec::with_capacity(self.width);
51 for i in 0..self.width {
52 let ix = (i * nx) / self.width.max(1);
53 let iy = (j * ny) / self.height.max(1);
54 let ix = ix.min(nx.saturating_sub(1));
55 let iy = iy.min(ny.saturating_sub(1));
56 if nx == 0 || ny == 0 {
57 row.push((' ', 0.0, 0.0, 0.0, 0.0));
58 continue;
59 }
60 let c = psi[ix][iy];
61 let prob = c.norm_sq() * self.brightness_scale;
62 let phase = c.arg();
63 let (r, g, b) = PhaseColorMap::phase_to_rgb(phase);
64 let ch = brightness_to_char(prob);
65 row.push((ch, r, g, b, prob));
66 }
67 grid.push(row);
68 }
69 grid
70 }
71}
72
73fn brightness_to_char(b: f64) -> char {
74 let chars = [' ', '.', ':', '-', '=', '+', '*', '#', '%', '@'];
75 let idx = (b * (chars.len() - 1) as f64).round() as usize;
76 chars[idx.min(chars.len() - 1)]
77}
78
79pub fn gaussian_wavepacket(x0: f64, k0: f64, sigma: f64, x_grid: &[f64]) -> Vec<Complex> {
81 let norm = 1.0 / (sigma * (2.0 * PI).sqrt()).sqrt();
82 x_grid
83 .iter()
84 .map(|&x| {
85 let gauss = (-((x - x0) * (x - x0)) / (4.0 * sigma * sigma)).exp();
86 let phase = k0 * x;
87 Complex::new(norm * gauss * phase.cos(), norm * gauss * phase.sin())
88 })
89 .collect()
90}
91
92pub fn plane_wave(k: f64, x_grid: &[f64]) -> Vec<Complex> {
94 let n = x_grid.len();
95 let norm = 1.0 / (n as f64).sqrt();
96 x_grid
97 .iter()
98 .map(|&x| {
99 let phase = k * x;
100 Complex::new(norm * phase.cos(), norm * phase.sin())
101 })
102 .collect()
103}
104
105pub fn coherent_state(n_max: usize, alpha: Complex, x_grid: &[f64], omega: f64, mass: f64, hbar: f64) -> Vec<Complex> {
107 let mut psi = vec![Complex::zero(); x_grid.len()];
108 let alpha_sq = alpha.norm_sq();
109 let prefactor = (-alpha_sq / 2.0).exp();
110
111 for n in 0..n_max {
112 let mut alpha_n = Complex::one();
114 for _ in 0..n {
115 alpha_n = alpha_n * alpha;
116 }
117 let n_fact: f64 = (1..=n).map(|k| k as f64).product::<f64>().max(1.0);
118 let c_n = alpha_n * (prefactor / n_fact.sqrt());
119
120 for (i, &x) in x_grid.iter().enumerate() {
121 let phi_n = super::harmonic::qho_wavefunction(n as u32, x, omega, mass, hbar);
122 psi[i] += c_n * phi_n;
123 }
124 }
125 psi
126}
127
128pub fn wigner_function(psi: &[Complex], x_grid: &[f64], p_grid: &[f64], dx: f64, hbar: f64) -> Vec<Vec<f64>> {
130 let nx = x_grid.len();
131 let np = p_grid.len();
132 let n_psi = psi.len();
133 let mut w = vec![vec![0.0; np]; nx];
134
135 for (ix, &x) in x_grid.iter().enumerate() {
136 for (ip, &p) in p_grid.iter().enumerate() {
137 let mut integral = 0.0;
138 let max_y_steps = (n_psi / 2).min(50);
140 let dy = dx;
141 for k in 0..max_y_steps {
142 let y = k as f64 * dy;
143 let x_plus = x + y;
144 let x_minus = x - y;
145
146 let psi_plus = interpolate_psi(psi, x_grid, x_plus);
148 let psi_minus = interpolate_psi(psi, x_grid, x_minus);
149
150 let phase = Complex::from_polar(1.0, 2.0 * p * y / hbar);
151 let integrand = psi_minus.conj() * psi_plus * phase;
152
153 let weight = if k == 0 { 1.0 } else { 2.0 }; integral += integrand.re * weight * dy;
155 }
156 w[ix][ip] = integral / (PI * hbar);
157 }
158 }
159 w
160}
161
162fn interpolate_psi(psi: &[Complex], x_grid: &[f64], x: f64) -> Complex {
163 if x_grid.is_empty() {
164 return Complex::zero();
165 }
166 let n = x_grid.len();
167 let x_min = x_grid[0];
168 let dx = if n > 1 { x_grid[1] - x_grid[0] } else { 1.0 };
169 let idx_f = (x - x_min) / dx;
170 if idx_f < 0.0 || idx_f >= (n - 1) as f64 {
171 return Complex::zero();
172 }
173 let idx = idx_f as usize;
174 let t = idx_f - idx as f64;
175 psi[idx] * (1.0 - t) + psi[idx + 1] * t
176}
177
178pub fn momentum_space(psi: &[Complex], dx: f64) -> Vec<Complex> {
180 let mut result = dft(psi);
181 let n = result.len();
182 let norm = (dx / (2.0 * PI).sqrt());
183 for c in &mut result {
184 *c = *c * norm;
185 }
186 result
187}
188
189pub struct PhaseColorMap;
191
192impl PhaseColorMap {
193 pub fn phase_to_rgb(phase: f64) -> (f64, f64, f64) {
195 let hue = (phase + PI) / (2.0 * PI); let hue = hue.fract();
197 Self::hsv_to_rgb(hue, 1.0, 1.0)
198 }
199
200 pub fn hsv_to_rgb(h: f64, s: f64, v: f64) -> (f64, f64, f64) {
201 let h = h * 6.0;
202 let c = v * s;
203 let x = c * (1.0 - (h % 2.0 - 1.0).abs());
204 let m = v - c;
205 let (r, g, b) = if h < 1.0 {
206 (c, x, 0.0)
207 } else if h < 2.0 {
208 (x, c, 0.0)
209 } else if h < 3.0 {
210 (0.0, c, x)
211 } else if h < 4.0 {
212 (0.0, x, c)
213 } else if h < 5.0 {
214 (x, 0.0, c)
215 } else {
216 (c, 0.0, x)
217 };
218 (r + m, g + m, b + m)
219 }
220}
221
222#[derive(Clone, Debug)]
224pub struct DensityMatrix {
225 pub rho: Vec<Vec<Complex>>,
226}
227
228impl DensityMatrix {
229 pub fn new(rho: Vec<Vec<Complex>>) -> Self {
230 Self { rho }
231 }
232
233 pub fn from_pure_state(psi: &[Complex]) -> Self {
234 let n = psi.len();
235 let mut rho = vec![vec![Complex::zero(); n]; n];
236 for i in 0..n {
237 for j in 0..n {
238 rho[i][j] = psi[i] * psi[j].conj();
239 }
240 }
241 Self { rho }
242 }
243
244 pub fn dim(&self) -> usize {
245 self.rho.len()
246 }
247
248 pub fn trace(&self) -> Complex {
249 let n = self.dim();
250 let mut t = Complex::zero();
251 for i in 0..n {
252 t += self.rho[i][i];
253 }
254 t
255 }
256
257 pub fn purity(&self) -> f64 {
259 let n = self.dim();
260 let mut sum = 0.0;
261 for i in 0..n {
262 for j in 0..n {
263 sum += (self.rho[i][j] * self.rho[j][i]).re;
264 }
265 }
266 sum
267 }
268
269 pub fn von_neumann_entropy(&self) -> f64 {
273 let n = self.dim();
274 if n == 2 {
275 let tr = self.rho[0][0].re + self.rho[1][1].re;
277 let det = (self.rho[0][0] * self.rho[1][1] - self.rho[0][1] * self.rho[1][0]).re;
278 let disc = (tr * tr - 4.0 * det).max(0.0).sqrt();
279 let l1 = ((tr + disc) / 2.0).max(1e-30);
280 let l2 = ((tr - disc) / 2.0).max(1e-30);
281 let mut s = 0.0;
282 if l1 > 1e-20 { s -= l1 * l1.ln(); }
283 if l2 > 1e-20 { s -= l2 * l2.ln(); }
284 return s;
285 }
286 let mut s = 0.0;
289 for i in 0..n {
290 let p = self.rho[i][i].re;
291 if p > 1e-20 {
292 s -= p * p.ln();
293 }
294 }
295 s
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn test_gaussian_wavepacket() {
305 let n = 512;
306 let dx = 0.05;
307 let x_grid: Vec<f64> = (0..n).map(|i| -12.8 + i as f64 * dx).collect();
308 let psi = gaussian_wavepacket(0.0, 0.0, 1.0, &x_grid);
309 let norm_sq: f64 = psi.iter().map(|c| c.norm_sq()).sum::<f64>() * dx;
310 assert!((norm_sq - 1.0).abs() < 0.05, "Norm: {}", norm_sq);
311 }
312
313 #[test]
314 fn test_gaussian_width() {
315 let n = 1024;
316 let dx = 0.05;
317 let sigma = 2.0;
318 let x_grid: Vec<f64> = (0..n).map(|i| -25.0 + i as f64 * dx).collect();
319 let psi = gaussian_wavepacket(0.0, 0.0, sigma, &x_grid);
320 let norm_sq: f64 = psi.iter().map(|c| c.norm_sq()).sum::<f64>() * dx;
322 let mean_x: f64 = psi.iter().enumerate().map(|(i, c)| c.norm_sq() * x_grid[i]).sum::<f64>() * dx / norm_sq;
323 let var_x: f64 = psi.iter().enumerate().map(|(i, c)| c.norm_sq() * (x_grid[i] - mean_x).powi(2)).sum::<f64>() * dx / norm_sq;
324 let measured_sigma = var_x.sqrt();
325 assert!((measured_sigma - sigma).abs() < 0.3, "Sigma: {}", measured_sigma);
326 }
327
328 #[test]
329 fn test_momentum_space_gaussian_reciprocal_width() {
330 let n = 256;
331 let dx = 0.1;
332 let sigma = 1.0;
333 let x_grid: Vec<f64> = (0..n).map(|i| -12.8 + i as f64 * dx).collect();
334 let psi = gaussian_wavepacket(0.0, 0.0, sigma, &x_grid);
335 let psi_k = momentum_space(&psi, dx);
336 let dk = 2.0 * PI / (n as f64 * dx);
338 let norm_k: f64 = psi_k.iter().map(|c| c.norm_sq()).sum::<f64>() * dk;
339 assert!(norm_k > 0.0);
341 }
342
343 #[test]
344 fn test_plane_wave() {
345 let n = 64;
346 let x_grid: Vec<f64> = (0..n).map(|i| i as f64 * 0.1).collect();
347 let psi = plane_wave(1.0, &x_grid);
348 let prob: Vec<f64> = psi.iter().map(|c| c.norm_sq()).collect();
350 let expected = 1.0 / n as f64;
351 for p in &prob {
352 assert!((p - expected).abs() < 1e-10);
353 }
354 }
355
356 #[test]
357 fn test_phase_color_map() {
358 let (r, g, b) = PhaseColorMap::phase_to_rgb(0.0);
359 assert!(r >= 0.0 && r <= 1.0);
361 assert!(g >= 0.0 && g <= 1.0);
362 assert!(b >= 0.0 && b <= 1.0);
363 }
364
365 #[test]
366 fn test_density_matrix_pure_state() {
367 let psi = vec![
368 Complex::new(1.0 / 2.0_f64.sqrt(), 0.0),
369 Complex::new(1.0 / 2.0_f64.sqrt(), 0.0),
370 ];
371 let dm = DensityMatrix::from_pure_state(&psi);
372 let purity = dm.purity();
373 assert!((purity - 1.0).abs() < 1e-10, "Purity: {}", purity);
374 let entropy = dm.von_neumann_entropy();
375 assert!(entropy.abs() < 0.01, "Entropy: {}", entropy);
376 }
377
378 #[test]
379 fn test_density_matrix_mixed_state() {
380 let rho = vec![
382 vec![Complex::new(0.5, 0.0), Complex::zero()],
383 vec![Complex::zero(), Complex::new(0.5, 0.0)],
384 ];
385 let dm = DensityMatrix::new(rho);
386 let purity = dm.purity();
387 assert!((purity - 0.5).abs() < 1e-10, "Purity: {}", purity);
388 let entropy = dm.von_neumann_entropy();
389 assert!((entropy - 2.0_f64.ln()).abs() < 0.01, "Entropy: {}", entropy);
390 }
391
392 #[test]
393 fn test_renderer_1d() {
394 let n = 64;
395 let dx = 0.1;
396 let x_grid: Vec<f64> = (0..n).map(|i| -3.2 + i as f64 * dx).collect();
397 let psi = gaussian_wavepacket(0.0, 0.0, 1.0, &x_grid);
398 let wf = WaveFunction1D::new(psi, dx, -3.2);
399 let renderer = WaveFunctionRenderer1D::new(40);
400 let result = renderer.render(&wf);
401 assert_eq!(result.len(), 40);
402 }
403
404 #[test]
405 fn test_renderer_2d() {
406 let nx = 16;
407 let ny = 16;
408 let psi: Vec<Vec<Complex>> = (0..nx)
409 .map(|i| {
410 (0..ny)
411 .map(|j| {
412 let r2 = (i as f64 - 8.0).powi(2) + (j as f64 - 8.0).powi(2);
413 Complex::new((-r2 / 4.0).exp(), 0.0)
414 })
415 .collect()
416 })
417 .collect();
418 let renderer = WaveFunctionRenderer2D::new(10, 10);
419 let result = renderer.render(&psi);
420 assert_eq!(result.len(), 10);
421 assert_eq!(result[0].len(), 10);
422 }
423
424 #[test]
425 fn test_wigner_function_runs() {
426 let n = 32;
427 let dx = 0.3;
428 let x_grid: Vec<f64> = (0..n).map(|i| -4.8 + i as f64 * dx).collect();
429 let psi = gaussian_wavepacket(0.0, 0.0, 1.0, &x_grid);
430 let p_grid: Vec<f64> = (0..8).map(|i| -2.0 + i as f64 * 0.5).collect();
431 let w = wigner_function(&psi, &x_grid, &p_grid, dx, 1.0);
432 assert_eq!(w.len(), n);
433 assert_eq!(w[0].len(), 8);
434 }
435}