use crate::detection::FLOOR;
use comnum_core::signal::Symbols;
use comnum_core::traits::Precode;
use comnum_core::{Error, Result, ensure};
use comnum_math::C64;
use comnum_math::linalg::{CMat, hermitian, identity, inverse_hpd, svd};
use ndarray::{Array2, ArrayView2};
pub fn water_filling_powers(gains: &[f64], total_power: f64) -> Result<Vec<f64>> {
ensure(total_power > 0.0, || format!("the total power must be positive, got {total_power}"))?;
ensure(gains.iter().all(|g| *g >= 0.0 && g.is_finite()), || "the gains must be non-negative".into())?;
let mut order: Vec<usize> = (0..gains.len()).filter(|&i| gains[i] > 0.0).collect();
order.sort_by(|a, b| gains[*b].total_cmp(&gains[*a]));
let mut p = vec![0.0; gains.len()];
for active in (1..=order.len()).rev() {
let inv: f64 = order[..active].iter().map(|&i| 1.0 / gains[i]).sum();
let mu = (total_power + inv) / active as f64;
if mu > 1.0 / gains[order[active - 1]] {
for &i in &order[..active] {
p[i] = mu - 1.0 / gains[i];
}
break;
}
}
Ok(p)
}
#[derive(Clone, Debug, PartialEq)]
pub struct Eigenmode {
pub precoder: CMat,
pub combiner: CMat,
pub gains: Vec<f64>,
pub powers: Vec<f64>,
}
impl Eigenmode {
pub fn new(h: &ArrayView2<'_, C64>, total_power: f64, noise_var: f64, water_filling: bool) -> Result<Eigenmode> {
ensure(noise_var > 0.0, || format!("the noise variance must be positive, got {noise_var}"))?;
let (u, s, v) = svd(h);
let nonzero: Vec<usize> = (0..s.len()).filter(|&i| s[i] > 1e-12 * s[0].max(FLOOR)).collect();
ensure(!nonzero.is_empty(), || "the channel is zero".into())?;
let powers_all = if water_filling {
water_filling_powers(&s.iter().map(|x| x * x / noise_var).collect::<Vec<_>>(), total_power)?
} else {
let p = total_power / nonzero.len() as f64;
(0..s.len()).map(|i| if nonzero.contains(&i) { p } else { 0.0 }).collect()
};
let active: Vec<usize> = (0..s.len()).filter(|&i| powers_all[i] > 0.0).collect();
let precoder =
Array2::from_shape_fn((h.ncols(), active.len()), |(r, c)| v[[r, active[c]]] * powers_all[active[c]].sqrt());
let combiner = Array2::from_shape_fn((active.len(), h.nrows()), |(r, c)| u[[c, active[r]]].conj());
Ok(Eigenmode {
precoder,
combiner,
gains: active.iter().map(|&i| s[i]).collect(),
powers: active.iter().map(|&i| powers_all[i]).collect(),
})
}
pub fn n_streams(&self) -> usize {
self.powers.len()
}
}
impl Precode for Eigenmode {
fn precode_into(&self, x: &Symbols, out: &mut Symbols) -> Result<()> {
precode_with(&self.precoder, x, out)
}
}
fn precode_with(w: &CMat, x: &Symbols, out: &mut Symbols) -> Result<()> {
if x.n_streams() != w.ncols() {
return Err(Error::Shape(format!("{} streams for a precoder of {}", x.n_streams(), w.ncols())));
}
out.assign(&w.dot(x.as_array()));
Ok(())
}
#[derive(Clone, Debug, PartialEq)]
pub struct LinearPrecoder {
pub w: CMat,
}
impl LinearPrecoder {
pub fn zero_forcing(h: &ArrayView2<'_, C64>, total_power: f64) -> Result<LinearPrecoder> {
ensure(h.nrows() <= h.ncols(), || {
format!("zero forcing serves at most {} users, not {}", h.ncols(), h.nrows())
})?;
LinearPrecoder::regularized(h, 0.0, total_power)
}
pub fn regularized(h: &ArrayView2<'_, C64>, alpha: f64, total_power: f64) -> Result<LinearPrecoder> {
ensure(alpha >= 0.0 && total_power > 0.0, || "alpha ≥ 0 and a positive power".into())?;
let hh = hermitian(h);
let g = h.dot(&hh) + identity(h.nrows()).mapv(|x| x * alpha);
let inv = inverse_hpd(&g.view()).map_err(|_| Error::Numerical("the channel is rank deficient".into()))?;
let w = hh.dot(&inv);
let norm2: f64 = w.iter().map(C64::norm_sqr).sum();
Ok(LinearPrecoder { w: w.mapv(|x| x * (total_power / norm2).sqrt()) })
}
}
impl Precode for LinearPrecoder {
fn precode_into(&self, x: &Symbols, out: &mut Symbols) -> Result<()> {
precode_with(&self.w, x, out)
}
}
pub fn ris_co_phasing(h: &[C64], g: &[C64]) -> Result<Vec<f64>> {
comnum_core::ensure_shape(h.len() == g.len(), || format!("hops of {} and {} elements", h.len(), g.len()))?;
Ok(h.iter().zip(g).map(|(a, b)| -(a * b).arg()).collect())
}
#[cfg(test)]
mod tests {
use super::*;
use comnum_core::rng::Rng;
#[test]
fn co_phased_elements_add_in_amplitude() {
let mut r = Rng::new(3);
let (h, g): (Vec<C64>, Vec<C64>) = (0..32).map(|_| (r.complex_gaussian(), r.complex_gaussian())).unzip();
let phases = ris_co_phasing(&h, &g).unwrap();
let gain: C64 = h.iter().zip(&g).zip(&phases).map(|((a, b), p)| b * C64::from_polar(1.0, *p) * a).sum();
let amplitude: f64 = h.iter().zip(&g).map(|(a, b)| a.norm() * b.norm()).sum();
assert!((gain - amplitude).norm() < 1e-12 * amplitude);
assert!(ris_co_phasing(&h, &g[1..]).is_err());
}
fn channel(nr: usize, nt: usize, seed: u64) -> CMat {
let mut r = Rng::new(seed);
Array2::from_shape_fn((nr, nt), |_| r.complex_gaussian())
}
#[test]
fn water_filling_satisfies_its_conditions() {
let g = [3.0, 1.5, 0.4, 0.05];
for p in [0.1, 1.0, 10.0, 100.0] {
let w = water_filling_powers(&g, p).unwrap();
assert!((w.iter().sum::<f64>() - p).abs() < 1e-12);
let levels: Vec<f64> = (0..4).filter(|&i| w[i] > 0.0).map(|i| w[i] + 1.0 / g[i]).collect();
assert!(levels.windows(2).all(|l| (l[0] - l[1]).abs() < 1e-12));
for i in (0..4).filter(|&i| w[i] == 0.0) {
assert!(1.0 / g[i] >= levels[0] - 1e-12);
}
}
assert_eq!(water_filling_powers(&[1.0, 1.0], 2.0).unwrap(), vec![1.0, 1.0]);
assert!(water_filling_powers(&[1.0], 0.0).is_err());
}
#[test]
fn eigenmodes_diagonalise_the_channel() {
let h = channel(4, 3, 1);
let e = Eigenmode::new(&h.view(), 3.0, 0.1, false).unwrap();
let d = e.combiner.dot(&h).dot(&e.precoder);
for i in 0..e.n_streams() {
for j in 0..e.n_streams() {
let want = if i == j { e.gains[i] * e.powers[i].sqrt() } else { 0.0 };
assert!((d[[i, j]] - C64::new(want, 0.0)).norm() < 1e-10);
}
}
assert_eq!(Eigenmode::new(&h.view(), 1e-3, 1.0, true).unwrap().n_streams(), 1);
}
#[test]
fn zero_forcing_removes_the_interference_between_users() {
let h = channel(3, 4, 2);
let p = LinearPrecoder::zero_forcing(&h.view(), 2.0).unwrap();
let e = h.dot(&p.w);
for i in 0..3 {
for j in 0..3 {
if i != j {
assert!(e[[i, j]].norm() < 1e-10);
}
}
}
assert!((p.w.iter().map(C64::norm_sqr).sum::<f64>() - 2.0).abs() < 1e-12);
assert!(LinearPrecoder::zero_forcing(&channel(5, 4, 3).view(), 1.0).is_err());
let x = Symbols::new(Array2::from_elem((3, 5), C64::new(1.0, 0.0)));
let mut out = Symbols::default();
p.precode_into(&x, &mut out).unwrap();
assert_eq!((out.n_streams(), out.len()), (4, 5));
}
}