1use crate::detection::FLOOR;
52use comnum_core::signal::Symbols;
53use comnum_core::traits::Precode;
54use comnum_core::{Error, Result, ensure};
55use comnum_math::C64;
56use comnum_math::linalg::{CMat, hermitian, identity, inverse_hpd, svd};
57use ndarray::{Array2, ArrayView2};
58
59pub fn water_filling_powers(gains: &[f64], total_power: f64) -> Result<Vec<f64>> {
65 ensure(total_power > 0.0, || format!("the total power must be positive, got {total_power}"))?;
66 ensure(gains.iter().all(|g| *g >= 0.0 && g.is_finite()), || "the gains must be non-negative".into())?;
67 let mut order: Vec<usize> = (0..gains.len()).filter(|&i| gains[i] > 0.0).collect();
68 order.sort_by(|a, b| gains[*b].total_cmp(&gains[*a]));
69 let mut p = vec![0.0; gains.len()];
70 for active in (1..=order.len()).rev() {
72 let inv: f64 = order[..active].iter().map(|&i| 1.0 / gains[i]).sum();
73 let mu = (total_power + inv) / active as f64;
74 if mu > 1.0 / gains[order[active - 1]] {
75 for &i in &order[..active] {
76 p[i] = mu - 1.0 / gains[i];
77 }
78 break;
79 }
80 }
81 Ok(p)
82}
83
84#[derive(Clone, Debug, PartialEq)]
86pub struct Eigenmode {
87 pub precoder: CMat,
89 pub combiner: CMat,
91 pub gains: Vec<f64>,
93 pub powers: Vec<f64>,
95}
96
97impl Eigenmode {
98 pub fn new(h: &ArrayView2<'_, C64>, total_power: f64, noise_var: f64, water_filling: bool) -> Result<Eigenmode> {
106 ensure(noise_var > 0.0, || format!("the noise variance must be positive, got {noise_var}"))?;
107 let (u, s, v) = svd(h);
108 let nonzero: Vec<usize> = (0..s.len()).filter(|&i| s[i] > 1e-12 * s[0].max(FLOOR)).collect();
109 ensure(!nonzero.is_empty(), || "the channel is zero".into())?;
110 let powers_all = if water_filling {
111 water_filling_powers(&s.iter().map(|x| x * x / noise_var).collect::<Vec<_>>(), total_power)?
112 } else {
113 let p = total_power / nonzero.len() as f64;
114 (0..s.len()).map(|i| if nonzero.contains(&i) { p } else { 0.0 }).collect()
115 };
116 let active: Vec<usize> = (0..s.len()).filter(|&i| powers_all[i] > 0.0).collect();
117 let precoder =
118 Array2::from_shape_fn((h.ncols(), active.len()), |(r, c)| v[[r, active[c]]] * powers_all[active[c]].sqrt());
119 let combiner = Array2::from_shape_fn((active.len(), h.nrows()), |(r, c)| u[[c, active[r]]].conj());
120 Ok(Eigenmode {
121 precoder,
122 combiner,
123 gains: active.iter().map(|&i| s[i]).collect(),
124 powers: active.iter().map(|&i| powers_all[i]).collect(),
125 })
126 }
127
128 pub fn n_streams(&self) -> usize {
130 self.powers.len()
131 }
132}
133
134impl Precode for Eigenmode {
135 fn precode_into(&self, x: &Symbols, out: &mut Symbols) -> Result<()> {
136 precode_with(&self.precoder, x, out)
137 }
138}
139
140fn precode_with(w: &CMat, x: &Symbols, out: &mut Symbols) -> Result<()> {
142 if x.n_streams() != w.ncols() {
143 return Err(Error::Shape(format!("{} streams for a precoder of {}", x.n_streams(), w.ncols())));
144 }
145 out.assign(&w.dot(x.as_array()));
146 Ok(())
147}
148
149#[derive(Clone, Debug, PartialEq)]
151pub struct LinearPrecoder {
152 pub w: CMat,
154}
155
156impl LinearPrecoder {
157 pub fn zero_forcing(h: &ArrayView2<'_, C64>, total_power: f64) -> Result<LinearPrecoder> {
163 ensure(h.nrows() <= h.ncols(), || {
164 format!("zero forcing serves at most {} users, not {}", h.ncols(), h.nrows())
165 })?;
166 LinearPrecoder::regularized(h, 0.0, total_power)
167 }
168
169 pub fn regularized(h: &ArrayView2<'_, C64>, alpha: f64, total_power: f64) -> Result<LinearPrecoder> {
176 ensure(alpha >= 0.0 && total_power > 0.0, || "alpha ≥ 0 and a positive power".into())?;
177 let hh = hermitian(h);
178 let g = h.dot(&hh) + identity(h.nrows()).mapv(|x| x * alpha);
179 let inv = inverse_hpd(&g.view()).map_err(|_| Error::Numerical("the channel is rank deficient".into()))?;
180 let w = hh.dot(&inv);
181 let norm2: f64 = w.iter().map(C64::norm_sqr).sum();
182 Ok(LinearPrecoder { w: w.mapv(|x| x * (total_power / norm2).sqrt()) })
183 }
184}
185
186impl Precode for LinearPrecoder {
187 fn precode_into(&self, x: &Symbols, out: &mut Symbols) -> Result<()> {
188 precode_with(&self.w, x, out)
189 }
190}
191
192pub fn ris_co_phasing(h: &[C64], g: &[C64]) -> Result<Vec<f64>> {
199 comnum_core::ensure_shape(h.len() == g.len(), || format!("hops of {} and {} elements", h.len(), g.len()))?;
200 Ok(h.iter().zip(g).map(|(a, b)| -(a * b).arg()).collect())
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use comnum_core::rng::Rng;
207
208 #[test]
209 fn co_phased_elements_add_in_amplitude() {
210 let mut r = Rng::new(3);
211 let (h, g): (Vec<C64>, Vec<C64>) = (0..32).map(|_| (r.complex_gaussian(), r.complex_gaussian())).unzip();
212 let phases = ris_co_phasing(&h, &g).unwrap();
213 let gain: C64 = h.iter().zip(&g).zip(&phases).map(|((a, b), p)| b * C64::from_polar(1.0, *p) * a).sum();
214 let amplitude: f64 = h.iter().zip(&g).map(|(a, b)| a.norm() * b.norm()).sum();
215 assert!((gain - amplitude).norm() < 1e-12 * amplitude);
216 assert!(ris_co_phasing(&h, &g[1..]).is_err());
217 }
218
219 fn channel(nr: usize, nt: usize, seed: u64) -> CMat {
220 let mut r = Rng::new(seed);
221 Array2::from_shape_fn((nr, nt), |_| r.complex_gaussian())
222 }
223
224 #[test]
225 fn water_filling_satisfies_its_conditions() {
226 let g = [3.0, 1.5, 0.4, 0.05];
227 for p in [0.1, 1.0, 10.0, 100.0] {
228 let w = water_filling_powers(&g, p).unwrap();
229 assert!((w.iter().sum::<f64>() - p).abs() < 1e-12);
230 let levels: Vec<f64> = (0..4).filter(|&i| w[i] > 0.0).map(|i| w[i] + 1.0 / g[i]).collect();
232 assert!(levels.windows(2).all(|l| (l[0] - l[1]).abs() < 1e-12));
233 for i in (0..4).filter(|&i| w[i] == 0.0) {
234 assert!(1.0 / g[i] >= levels[0] - 1e-12);
235 }
236 }
237 assert_eq!(water_filling_powers(&[1.0, 1.0], 2.0).unwrap(), vec![1.0, 1.0]);
238 assert!(water_filling_powers(&[1.0], 0.0).is_err());
239 }
240
241 #[test]
242 fn eigenmodes_diagonalise_the_channel() {
243 let h = channel(4, 3, 1);
244 let e = Eigenmode::new(&h.view(), 3.0, 0.1, false).unwrap();
245 let d = e.combiner.dot(&h).dot(&e.precoder);
246 for i in 0..e.n_streams() {
247 for j in 0..e.n_streams() {
248 let want = if i == j { e.gains[i] * e.powers[i].sqrt() } else { 0.0 };
249 assert!((d[[i, j]] - C64::new(want, 0.0)).norm() < 1e-10);
250 }
251 }
252 assert_eq!(Eigenmode::new(&h.view(), 1e-3, 1.0, true).unwrap().n_streams(), 1);
254 }
255
256 #[test]
257 fn zero_forcing_removes_the_interference_between_users() {
258 let h = channel(3, 4, 2);
259 let p = LinearPrecoder::zero_forcing(&h.view(), 2.0).unwrap();
260 let e = h.dot(&p.w);
261 for i in 0..3 {
262 for j in 0..3 {
263 if i != j {
264 assert!(e[[i, j]].norm() < 1e-10);
265 }
266 }
267 }
268 assert!((p.w.iter().map(C64::norm_sqr).sum::<f64>() - 2.0).abs() < 1e-12);
269 assert!(LinearPrecoder::zero_forcing(&channel(5, 4, 3).view(), 1.0).is_err());
270 let x = Symbols::new(Array2::from_elem((3, 5), C64::new(1.0, 0.0)));
271 let mut out = Symbols::default();
272 p.precode_into(&x, &mut out).unwrap();
273 assert_eq!((out.n_streams(), out.len()), (4, 5));
274 }
275}