1use crate::error::{CsError, CsResult};
34use crate::handle::LcgRng;
35
36#[inline]
41fn cmul(ar: f64, ai: f64, br: f64, bi: f64) -> (f64, f64) {
42 (ar * br - ai * bi, ar * bi + ai * br)
43}
44
45fn dft(x: &[f64], n: usize) -> Vec<f64> {
49 let mut out = vec![0.0_f64; 2 * n];
50 let two_pi = std::f64::consts::TAU;
51 for k in 0..n {
52 let mut sr = 0.0_f64;
53 let mut si = 0.0_f64;
54 for j in 0..n {
55 let angle = -two_pi * (j as f64) * (k as f64) / (n as f64);
56 let (wr, wi) = (angle.cos(), angle.sin());
57 let xr = x[2 * j];
58 let xi = x[2 * j + 1];
59 let (pr, pi) = cmul(xr, xi, wr, wi);
60 sr += pr;
61 si += pi;
62 }
63 out[2 * k] = sr;
64 out[2 * k + 1] = si;
65 }
66 out
67}
68
69fn idft(x: &[f64], n: usize) -> Vec<f64> {
71 let mut out = vec![0.0_f64; 2 * n];
72 let two_pi = std::f64::consts::TAU;
73 let inv_n = 1.0 / (n as f64);
74 for k in 0..n {
75 let mut sr = 0.0_f64;
76 let mut si = 0.0_f64;
77 for j in 0..n {
78 let angle = two_pi * (j as f64) * (k as f64) / (n as f64);
79 let (wr, wi) = (angle.cos(), angle.sin());
80 let xr = x[2 * j];
81 let xi = x[2 * j + 1];
82 let (pr, pi) = cmul(xr, xi, wr, wi);
83 sr += pr;
84 si += pi;
85 }
86 out[2 * k] = sr * inv_n;
87 out[2 * k + 1] = si * inv_n;
88 }
89 out
90}
91
92#[derive(Debug, Clone)]
98pub struct CodedDiffraction {
99 pub n: usize,
101 pub n_masks: usize,
103 masks: Vec<f64>,
106}
107
108#[derive(Debug, Clone, Copy)]
110pub enum MaskKind {
111 Octanary,
114 UniformPhase,
116 Rademacher,
118}
119
120impl CodedDiffraction {
121 pub fn new(n: usize, n_masks: usize, kind: MaskKind, rng: &mut LcgRng) -> CsResult<Self> {
126 if n == 0 || n_masks == 0 {
127 return Err(CsError::InvalidParameter(
128 "coded diffraction: n and n_masks must be > 0".into(),
129 ));
130 }
131 let mut masks = vec![0.0_f64; n_masks * 2 * n];
132 for l in 0..n_masks {
133 for j in 0..n {
134 let (re, im) = sample_mask_entry(kind, rng);
135 masks[l * 2 * n + 2 * j] = re;
136 masks[l * 2 * n + 2 * j + 1] = im;
137 }
138 }
139 Ok(Self { n, n_masks, masks })
140 }
141
142 #[must_use]
144 pub fn n_measurements(&self) -> usize {
145 self.n_masks * self.n
146 }
147
148 fn mask(&self, l: usize) -> &[f64] {
150 &self.masks[l * 2 * self.n..(l + 1) * 2 * self.n]
151 }
152
153 fn apply(&self, l: usize, x: &[f64]) -> Vec<f64> {
157 let d = self.mask(l);
158 let mut mod_x = vec![0.0_f64; 2 * self.n];
159 for j in 0..self.n {
160 let (pr, pi) = cmul(d[2 * j], d[2 * j + 1], x[2 * j], x[2 * j + 1]);
161 mod_x[2 * j] = pr;
162 mod_x[2 * j + 1] = pi;
163 }
164 dft(&mod_x, self.n)
165 }
166
167 fn apply_adjoint(&self, l: usize, z: &[f64]) -> Vec<f64> {
170 let d = self.mask(l);
171 let inv = idft(z, self.n);
172 let mut out = vec![0.0_f64; 2 * self.n];
173 for j in 0..self.n {
174 let (pr, pi) = cmul(d[2 * j], -d[2 * j + 1], inv[2 * j], inv[2 * j + 1]);
176 out[2 * j] = pr;
177 out[2 * j + 1] = pi;
178 }
179 out
180 }
181
182 pub fn forward(&self, x: &[f64]) -> CsResult<Vec<f64>> {
190 if x.len() != 2 * self.n {
191 return Err(CsError::DimensionMismatch {
192 a: x.len(),
193 b: 2 * self.n,
194 });
195 }
196 let mut y = vec![0.0_f64; self.n_measurements()];
197 for l in 0..self.n_masks {
198 let ax = self.apply(l, x);
199 for k in 0..self.n {
200 let re = ax[2 * k];
201 let im = ax[2 * k + 1];
202 y[l * self.n + k] = re * re + im * im;
203 }
204 }
205 Ok(y)
206 }
207
208 pub fn wirtinger_flow(
220 &self,
221 y: &[f64],
222 cfg: &WirtingerConfig,
223 rng: &mut LcgRng,
224 ) -> CsResult<Vec<f64>> {
225 let m = self.n_measurements();
226 if y.len() != m {
227 return Err(CsError::DimensionMismatch { a: y.len(), b: m });
228 }
229
230 let sum_y: f64 = y.iter().sum();
233 let lambda_sq = (self.n as f64) * sum_y / (m as f64);
234 let lambda = lambda_sq.max(1e-30).sqrt();
235
236 let mut z = random_unit_complex(self.n, rng);
239 for _ in 0..cfg.power_iters {
240 let yz = self.apply_y(y, &z, m);
241 let nrm = cnorm(&yz);
242 if nrm < 1e-300 {
243 return Err(CsError::NumericalInstability(
244 "WF spectral init: degenerate leading eigenvector".into(),
245 ));
246 }
247 for v in z.iter_mut() {
248 *v = 0.0;
249 }
250 for j in 0..2 * self.n {
251 z[j] = yz[j] / nrm;
252 }
253 }
254 for v in z.iter_mut() {
256 *v *= lambda;
257 }
258
259 let step = cfg.step_size / lambda_sq.max(1e-30);
263 for _ in 0..cfg.max_iter {
264 let grad = self.wf_gradient(y, &z, m);
265 for j in 0..2 * self.n {
266 z[j] -= step * grad[j];
267 }
268 }
269
270 Ok(z)
271 }
272
273 fn apply_y(&self, y: &[f64], z: &[f64], m: usize) -> Vec<f64> {
275 let inv_m = 1.0 / (m as f64);
276 let mut acc = vec![0.0_f64; 2 * self.n];
277 for l in 0..self.n_masks {
278 let az = self.apply(l, z); let mut w = vec![0.0_f64; 2 * self.n];
282 for k in 0..self.n {
283 let yr = y[l * self.n + k];
284 w[2 * k] = yr * az[2 * k];
286 w[2 * k + 1] = yr * az[2 * k + 1];
287 }
288 let contrib = self.apply_adjoint(l, &w);
289 for j in 0..2 * self.n {
290 acc[j] += contrib[j];
291 }
292 }
293 for v in acc.iter_mut() {
294 *v *= inv_m;
295 }
296 acc
297 }
298
299 fn wf_gradient(&self, y: &[f64], z: &[f64], m: usize) -> Vec<f64> {
301 let inv_m = 1.0 / (m as f64);
302 let mut grad = vec![0.0_f64; 2 * self.n];
303 for l in 0..self.n_masks {
304 let az = self.apply(l, z);
305 let mut resid = vec![0.0_f64; 2 * self.n];
306 for k in 0..self.n {
307 let re = az[2 * k];
308 let im = az[2 * k + 1];
309 let mag_sq = re * re + im * im;
310 let factor = mag_sq - y[l * self.n + k]; resid[2 * k] = factor * re;
312 resid[2 * k + 1] = factor * im;
313 }
314 let contrib = self.apply_adjoint(l, &resid);
315 for j in 0..2 * self.n {
316 grad[j] += contrib[j];
317 }
318 }
319 for v in grad.iter_mut() {
320 *v *= inv_m;
321 }
322 grad
323 }
324}
325
326#[derive(Debug, Clone)]
328pub struct WirtingerConfig {
329 pub power_iters: usize,
331 pub max_iter: usize,
333 pub step_size: f64,
335}
336
337impl Default for WirtingerConfig {
338 fn default() -> Self {
339 Self {
340 power_iters: 50,
341 max_iter: 400,
342 step_size: 0.2,
343 }
344 }
345}
346
347fn cnorm(x: &[f64]) -> f64 {
353 x.iter().map(|v| v * v).sum::<f64>().sqrt()
354}
355
356fn random_unit_complex(n: usize, rng: &mut LcgRng) -> Vec<f64> {
358 let mut z = vec![0.0_f64; 2 * n];
359 for v in z.iter_mut() {
360 *v = rng.next_normal();
361 }
362 let nrm = cnorm(&z).max(1e-300);
363 for v in z.iter_mut() {
364 *v /= nrm;
365 }
366 z
367}
368
369fn sample_mask_entry(kind: MaskKind, rng: &mut LcgRng) -> (f64, f64) {
371 match kind {
372 MaskKind::Octanary => {
373 let phase = rng.next_usize(4);
375 let (pr, pi) = match phase {
376 0 => (1.0, 0.0),
377 1 => (-1.0, 0.0),
378 2 => (0.0, 1.0),
379 _ => (0.0, -1.0),
380 };
381 let b2 = if rng.next_f64() < 0.8 {
382 std::f64::consts::FRAC_1_SQRT_2
383 } else {
384 3.0_f64.sqrt()
385 };
386 (pr * b2, pi * b2)
387 }
388 MaskKind::UniformPhase => {
389 let theta = std::f64::consts::TAU * rng.next_f64();
390 (theta.cos(), theta.sin())
391 }
392 MaskKind::Rademacher => {
393 if rng.next_bool() {
394 (1.0, 0.0)
395 } else {
396 (-1.0, 0.0)
397 }
398 }
399 }
400}
401
402pub fn phase_aligned_error(x_hat: &[f64], x: &[f64]) -> CsResult<f64> {
410 if x_hat.len() != x.len() {
411 return Err(CsError::DimensionMismatch {
412 a: x_hat.len(),
413 b: x.len(),
414 });
415 }
416 let mut ipr = 0.0_f64;
418 let mut ipi = 0.0_f64;
419 let n = x.len() / 2;
420 for k in 0..n {
421 let (ar, ai) = (x_hat[2 * k], x_hat[2 * k + 1]);
422 let (br, bi) = (x[2 * k], x[2 * k + 1]);
423 ipr += ar * br + ai * bi;
425 ipi += ar * bi - ai * br;
426 }
427 let mag = (ipr * ipr + ipi * ipi).sqrt();
428 let (cr, ci) = if mag > 1e-300 {
429 (ipr / mag, ipi / mag)
430 } else {
431 (1.0, 0.0)
432 };
433 let mut err_sq = 0.0_f64;
435 let mut x_sq = 0.0_f64;
436 for k in 0..n {
437 let (ar, ai) = (x_hat[2 * k], x_hat[2 * k + 1]);
438 let (rr, ri) = cmul(ar, ai, cr, ci);
439 let dr = rr - x[2 * k];
440 let di = ri - x[2 * k + 1];
441 err_sq += dr * dr + di * di;
442 x_sq += x[2 * k] * x[2 * k] + x[2 * k + 1] * x[2 * k + 1];
443 }
444 Ok((err_sq / x_sq.max(1e-300)).sqrt())
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450
451 fn complex_signal(reals: &[f64], imags: &[f64]) -> Vec<f64> {
452 let mut x = Vec::with_capacity(reals.len() * 2);
453 for (r, i) in reals.iter().zip(imags.iter()) {
454 x.push(*r);
455 x.push(*i);
456 }
457 x
458 }
459
460 #[test]
461 fn dft_idft_round_trip() {
462 let n = 6;
463 let x = complex_signal(
464 &[1.0, -2.0, 3.0, 0.5, -1.0, 2.0],
465 &[0.0, 1.0, -1.0, 0.0, 2.0, -0.5],
466 );
467 let back = idft(&dft(&x, n), n);
468 for k in 0..2 * n {
469 assert!(
470 (back[k] - x[k]).abs() < 1e-9,
471 "k={k}: {} vs {}",
472 back[k],
473 x[k]
474 );
475 }
476 }
477
478 #[test]
479 fn constructor_rejects_zero_dims() {
480 let mut rng = LcgRng::new(1);
481 assert!(CodedDiffraction::new(0, 4, MaskKind::Octanary, &mut rng).is_err());
482 assert!(CodedDiffraction::new(8, 0, MaskKind::Octanary, &mut rng).is_err());
483 }
484
485 #[test]
486 fn forward_shapes() {
487 let mut rng = LcgRng::new(2);
488 let cdp = CodedDiffraction::new(8, 5, MaskKind::UniformPhase, &mut rng).expect("ok");
489 assert_eq!(cdp.n_measurements(), 40);
490 let x = vec![0.1_f64; 16];
491 let y = cdp.forward(&x).expect("ok");
492 assert_eq!(y.len(), 40);
493 assert!(y.iter().all(|&v| v >= 0.0), "intensities non-negative");
494 }
495
496 #[test]
497 fn forward_dimension_mismatch() {
498 let mut rng = LcgRng::new(3);
499 let cdp = CodedDiffraction::new(8, 4, MaskKind::Octanary, &mut rng).expect("ok");
500 assert!(matches!(
501 cdp.forward(&[0.0; 10]),
502 Err(CsError::DimensionMismatch { .. })
503 ));
504 }
505
506 #[test]
507 fn forward_phase_invariant_intensity() {
508 let mut rng = LcgRng::new(4);
510 let cdp = CodedDiffraction::new(6, 4, MaskKind::UniformPhase, &mut rng).expect("ok");
511 let x = complex_signal(
512 &[1.0, 2.0, -1.0, 0.5, 0.0, 3.0],
513 &[0.0, -1.0, 1.0, 0.0, 2.0, 0.0],
514 );
515 let y0 = cdp.forward(&x).expect("ok");
516 let (cr, ci) = (
518 std::f64::consts::FRAC_PI_3.cos(),
519 std::f64::consts::FRAC_PI_3.sin(),
520 );
521 let mut x_rot = vec![0.0_f64; x.len()];
522 for k in 0..6 {
523 let (rr, ri) = cmul(x[2 * k], x[2 * k + 1], cr, ci);
524 x_rot[2 * k] = rr;
525 x_rot[2 * k + 1] = ri;
526 }
527 let y1 = cdp.forward(&x_rot).expect("ok");
528 for (a, b) in y0.iter().zip(y1.iter()) {
529 assert!((a - b).abs() < 1e-8, "{a} vs {b}");
530 }
531 }
532
533 #[test]
534 fn phase_aligned_error_zero_for_rotation() {
535 let x = complex_signal(&[1.0, -2.0, 0.5], &[0.5, 1.0, -1.0]);
536 let (cr, ci) = (0.6_f64, 0.8_f64); let mut x_rot = vec![0.0_f64; x.len()];
538 for k in 0..3 {
539 let (rr, ri) = cmul(x[2 * k], x[2 * k + 1], cr, ci);
540 x_rot[2 * k] = rr;
541 x_rot[2 * k + 1] = ri;
542 }
543 let err = phase_aligned_error(&x_rot, &x).expect("ok");
544 assert!(err < 1e-9, "err = {err}");
545 }
546
547 #[test]
548 fn phase_aligned_error_dim_mismatch() {
549 assert!(matches!(
550 phase_aligned_error(&[1.0, 0.0], &[1.0, 0.0, 0.0, 0.0]),
551 Err(CsError::DimensionMismatch { .. })
552 ));
553 }
554
555 #[test]
556 fn wirtinger_flow_recovers_real_signal() {
557 let n = 5usize;
561 let mut rng = LcgRng::new(20);
562 let cdp = CodedDiffraction::new(n, 10, MaskKind::Octanary, &mut rng).expect("ok");
563 let x = complex_signal(&[1.0, -1.0, 0.5, 2.0, -0.5], &[0.0; 5]);
564 let y = cdp.forward(&x).expect("ok");
565 let cfg = WirtingerConfig {
566 power_iters: 120,
567 max_iter: 2500,
568 step_size: 0.15,
569 };
570 let mut rng2 = LcgRng::new(99);
571 let x_hat = cdp.wirtinger_flow(&y, &cfg, &mut rng2).expect("ok");
572 let err = phase_aligned_error(&x_hat, &x).expect("ok");
573 assert!(err < 0.15, "relative error = {err}");
574 }
575
576 #[test]
577 fn wirtinger_flow_recovers_complex_signal() {
578 let n = 4usize;
579 let mut rng = LcgRng::new(31);
580 let cdp = CodedDiffraction::new(n, 8, MaskKind::Octanary, &mut rng).expect("ok");
581 let x = complex_signal(&[1.0, 0.0, -1.0, 0.5], &[0.5, 1.0, 0.0, -1.0]);
582 let y = cdp.forward(&x).expect("ok");
583 let cfg = WirtingerConfig {
584 power_iters: 100,
585 max_iter: 2000,
586 step_size: 0.15,
587 };
588 let mut rng2 = LcgRng::new(7);
589 let x_hat = cdp.wirtinger_flow(&y, &cfg, &mut rng2).expect("ok");
590 let err = phase_aligned_error(&x_hat, &x).expect("ok");
591 assert!(err < 0.2, "relative error = {err}");
592 }
593
594 #[test]
595 fn wirtinger_flow_dimension_mismatch() {
596 let mut rng = LcgRng::new(5);
597 let cdp = CodedDiffraction::new(8, 4, MaskKind::Octanary, &mut rng).expect("ok");
598 let mut rng2 = LcgRng::new(6);
599 assert!(matches!(
600 cdp.wirtinger_flow(&[0.0; 10], &WirtingerConfig::default(), &mut rng2),
601 Err(CsError::DimensionMismatch { .. })
602 ));
603 }
604
605 #[test]
606 fn recovered_signal_reproduces_measurements() {
607 let n = 4usize;
609 let mut rng = LcgRng::new(40);
610 let cdp = CodedDiffraction::new(n, 7, MaskKind::Octanary, &mut rng).expect("ok");
611 let x = complex_signal(&[2.0, -1.0, 0.0, 1.0], &[0.0, 0.5, -0.5, 0.0]);
612 let y = cdp.forward(&x).expect("ok");
613 let cfg = WirtingerConfig {
614 power_iters: 100,
615 max_iter: 2000,
616 step_size: 0.15,
617 };
618 let mut rng2 = LcgRng::new(8);
619 let x_hat = cdp.wirtinger_flow(&y, &cfg, &mut rng2).expect("ok");
620 let y_hat = cdp.forward(&x_hat).expect("ok");
621 let mut num = 0.0_f64;
622 let mut den = 0.0_f64;
623 for (a, b) in y_hat.iter().zip(y.iter()) {
624 num += (a - b) * (a - b);
625 den += b * b;
626 }
627 let rel = (num / den.max(1e-30)).sqrt();
628 assert!(rel < 0.2, "measurement reproduction error = {rel}");
629 }
630
631 #[test]
632 fn rademacher_masks_are_real() {
633 let mut rng = LcgRng::new(50);
634 let cdp = CodedDiffraction::new(6, 3, MaskKind::Rademacher, &mut rng).expect("ok");
635 for l in 0..3 {
636 let m = cdp.mask(l);
637 for j in 0..6 {
638 assert_eq!(m[2 * j + 1], 0.0, "imag part must be zero");
639 assert!((m[2 * j].abs() - 1.0).abs() < 1e-12, "must be ±1");
640 }
641 }
642 }
643
644 #[test]
645 fn zero_signal_zero_measurements() {
646 let mut rng = LcgRng::new(60);
647 let cdp = CodedDiffraction::new(5, 4, MaskKind::Octanary, &mut rng).expect("ok");
648 let y = cdp.forward(&vec![0.0_f64; 10]).expect("ok");
649 assert!(y.iter().all(|&v| v == 0.0));
650 }
651}