1pub fn nnls(a: &[Vec<f64>], b: &[f64]) -> Vec<f64> {
9 let n = a.len();
10 if n == 0 {
11 return vec![];
12 }
13 let m = b.len();
14 let mut x = vec![0.0f64; n];
15 let mut passive: Vec<usize> = Vec::new();
16 let mut active: Vec<usize> = (0..n).collect();
17
18 const TOL: f64 = 1e-10;
19 let max_iter = 3 * n + 10;
20
21 for _ in 0..max_iter {
22 let residual: Vec<f64> = {
24 let ax = matvec(a, &x, m);
25 (0..m).map(|i| b[i] - ax[i]).collect()
26 };
27 let w: Vec<f64> = active.iter().map(|&j| dot(&a[j], &residual)).collect();
28
29 let Some((best_pos, &best_w)) = w.iter().enumerate().max_by(|a, b| a.1.total_cmp(&b.1))
30 else {
31 break;
32 };
33 if active.is_empty() || best_w <= TOL {
34 break;
35 }
36 let j = active.remove(best_pos);
37 passive.push(j);
38
39 loop {
40 let z_passive = solve_normal_equations(
41 &passive.iter().map(|&j| a[j].clone()).collect::<Vec<_>>(),
42 b,
43 );
44 if z_passive.iter().all(|&v| v > TOL) {
45 for (idx, &j) in passive.iter().enumerate() {
46 x[j] = z_passive[idx];
47 }
48 break;
49 }
50 let mut alpha = f64::INFINITY;
52 for (idx, &j) in passive.iter().enumerate() {
53 if z_passive[idx] <= TOL {
54 let denom = x[j] - z_passive[idx];
55 if denom > 0.0 {
56 alpha = alpha.min(x[j] / denom);
57 }
58 }
59 }
60 if !alpha.is_finite() {
61 alpha = 0.0;
62 }
63 for (idx, &j) in passive.iter().enumerate() {
64 x[j] += alpha * (z_passive[idx] - x[j]);
65 }
66 let mut still_passive = Vec::new();
68 for &j in &passive {
69 if x[j] <= TOL {
70 x[j] = 0.0;
71 active.push(j);
72 } else {
73 still_passive.push(j);
74 }
75 }
76 passive = still_passive;
77 if passive.is_empty() {
78 break;
79 }
80 }
81 }
82 x
83}
84
85fn matvec(a: &[Vec<f64>], x: &[f64], m: usize) -> Vec<f64> {
86 let mut out = vec![0.0f64; m];
87 for (j, col) in a.iter().enumerate() {
88 if x[j] == 0.0 {
89 continue;
90 }
91 for i in 0..m {
92 out[i] += col[i] * x[j];
93 }
94 }
95 out
96}
97
98fn dot(a: &[f64], b: &[f64]) -> f64 {
99 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
100}
101
102fn solve_normal_equations(a_cols: &[Vec<f64>], b: &[f64]) -> Vec<f64> {
105 let k = a_cols.len();
106 if k == 0 {
107 return vec![];
108 }
109 let mut ata = vec![vec![0.0f64; k]; k];
110 let mut atb = vec![0.0f64; k];
111 for i in 0..k {
112 atb[i] = dot(&a_cols[i], b);
113 for j in 0..k {
114 ata[i][j] = dot(&a_cols[i], &a_cols[j]);
115 }
116 ata[i][i] += 1e-10; }
118 gaussian_solve(&mut ata, &mut atb)
119}
120
121fn gaussian_solve(a: &mut [Vec<f64>], b: &mut [f64]) -> Vec<f64> {
122 let n = b.len();
123 for col in 0..n {
124 let mut pivot_row = col;
126 let mut pivot_val = a[col][col].abs();
127 for row in (col + 1)..n {
128 if a[row][col].abs() > pivot_val {
129 pivot_val = a[row][col].abs();
130 pivot_row = row;
131 }
132 }
133 if pivot_val < 1e-14 {
134 continue; }
136 a.swap(col, pivot_row);
137 b.swap(col, pivot_row);
138 let diag = a[col][col];
139 for row in (col + 1)..n {
140 let factor = a[row][col] / diag;
141 if factor == 0.0 {
142 continue;
143 }
144 for k in col..n {
145 a[row][k] -= factor * a[col][k];
146 }
147 b[row] -= factor * b[col];
148 }
149 }
150 let mut x = vec![0.0f64; n];
151 for i in (0..n).rev() {
152 if a[i][i].abs() < 1e-14 {
153 x[i] = 0.0;
154 continue;
155 }
156 let mut sum = b[i];
157 for j in (i + 1)..n {
158 sum -= a[i][j] * x[j];
159 }
160 x[i] = sum / a[i][i];
161 }
162 x
163}
164
165pub fn finalize_weights(raw_weights: &[f64], nnls_beta: f64) -> Vec<f64> {
168 let n = raw_weights.len();
169 let sum: f64 = raw_weights.iter().sum();
170 let normalized: Vec<f64> = if sum > 0.0 {
171 raw_weights.iter().map(|&w| w / sum).collect()
172 } else {
173 vec![1.0 / n as f64; n]
174 };
175 let uniform = 1.0 / n as f64;
176 normalized
177 .iter()
178 .map(|&w| nnls_beta * w + (1.0 - nnls_beta) * uniform)
179 .collect()
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_nnls_exact_solution() {
188 let a = vec![vec![1.0, 0.0, 1.0], vec![0.0, 1.0, 1.0]];
190 let b = vec![1.0, 2.0, 3.0];
191 let x = nnls(&a, &b);
192 assert!((x[0] - 1.0).abs() < 1e-6, "x0={}", x[0]);
193 assert!((x[1] - 2.0).abs() < 1e-6, "x1={}", x[1]);
194 }
195
196 #[test]
197 fn test_nnls_enforces_nonnegativity() {
198 let a = vec![vec![-1.0, -1.0, -1.0]];
200 let b = vec![1.0, 1.0, 1.0];
201 let x = nnls(&a, &b);
202 assert!(x[0] >= 0.0);
203 }
204
205 #[test]
206 fn test_nnls_matches_scipy() {
207 let a = vec![
210 vec![
211 -0.8287016657127513,
212 -0.8117427155192016,
213 0.46915430281842907,
214 -0.1387439591716444,
215 -0.4315976725024171,
216 -0.9970198329823277,
217 0.7834221408903144,
218 -0.9393079846750576,
219 0.3210001348557896,
220 -0.4036738186851505,
221 ],
222 vec![
223 -0.5263789868078006,
224 -0.1337461195270524,
225 -0.7726559601571932,
226 0.17359714287628147,
227 0.2970944141596501,
228 0.9469205495328255,
229 0.17032587978181613,
230 0.413930191311247,
231 0.862927709482709,
232 0.48351336013866075,
233 ],
234 vec![
235 0.6025489304127938,
236 -0.04189740371833195,
237 -0.21754361900867591,
238 0.4756755745843204,
239 0.39243199334031087,
240 -0.4031975539662487,
241 -0.057380669636337256,
242 -0.2515123330430584,
243 -0.5856176638379975,
244 0.44432961628423495,
245 ],
246 vec![
247 0.16432407212873557,
248 -0.6805221707258429,
249 0.03348036524272735,
250 0.9125345096721971,
251 -0.41455850197502575,
252 -0.3720279959313264,
253 0.5465540192976328,
254 -0.8182945729914843,
255 0.26018039957068595,
256 -0.5625691508623909,
257 ],
258 ];
259 let b = vec![
260 0.8298868742743123,
261 0.6576522108732432,
262 0.6827989078603502,
263 0.820075750170535,
264 0.42857290429846195,
265 0.758705461154919,
266 0.8784801846662539,
267 0.1023199219220744,
268 0.8497683374661538,
269 0.39392733263233515,
270 ];
271 let expected = [
272 0.0,
273 0.4483466391927005,
274 0.283691444258291,
275 0.19757999070784782,
276 ];
277 let x = nnls(&a, &b);
278 for (g, e) in x.iter().zip(expected.iter()) {
279 assert!((g - e).abs() < 1e-4, "got {g} vs expected {e}");
280 }
281 }
282
283 #[test]
284 fn test_finalize_weights_sums_to_one_when_beta_1() {
285 let w = finalize_weights(&[1.0, 3.0], 1.0);
286 assert!((w[0] + w[1] - 1.0).abs() < 1e-9);
287 assert!((w[0] - 0.25).abs() < 1e-9);
288 }
289
290 #[test]
291 fn test_finalize_weights_uniform_when_beta_0() {
292 let w = finalize_weights(&[1.0, 3.0], 0.0);
293 assert!((w[0] - 0.5).abs() < 1e-9);
294 assert!((w[1] - 0.5).abs() < 1e-9);
295 }
296}