use std::time::{Duration, Instant};
use anyhow::Result;
use log::info;
use rayon::prelude::*;
use super::band::{
BandIndex, BandKernelPerf, calculate_ab_band, calculate_ab_band_with_masked_norm_sq,
calculate_forward_band, calculate_forward_band_with_norm_sq,
};
use super::nnls_ops::{EPS, PackedTriIndex, Scratch, zero_negative_entries};
use crate::weights::active_set_weights::Progress;
#[doc(hidden)]
#[allow(clippy::too_many_arguments)] pub fn cgnr(
x: &mut [f64],
d: &[f64],
active_set: &[bool],
max_iters: usize,
tol: f64,
scratch: &mut Scratch,
tri_idx: &PackedTriIndex,
kernel_perf: &mut BandKernelPerf,
track_kernel_perf: bool,
progress: Option<&dyn Progress>,
started: Instant,
max_time_ms: u64,
band: &BandIndex,
row_sums: &mut [f64],
) -> Result<usize> {
let n = tri_idx.n;
let (p, r, z, w) = (
&mut scratch.p,
&mut scratch.r,
&mut scratch.z,
&mut scratch.w,
);
zero_negative_entries(x);
let free_indices = collect_free_indices(active_set);
if free_indices.is_empty() {
return Ok(1);
}
let nfree = free_indices.len();
let mut p_free = vec![0.0; nfree];
let mut z_free = vec![0.0; nfree];
let mut row_sq_accum = vec![0.0f64; n];
let mut shifted = vec![0.0f64; n];
profiled_forward_band(
x,
r,
band,
row_sums,
&mut shifted,
kernel_perf,
track_kernel_perf,
);
r.iter_mut()
.zip(d.iter())
.for_each(|(ri, &di)| *ri = di - *ri);
let mut zts = profiled_adjoint_band_with_masked_norm(
r,
z,
active_set,
band,
row_sums,
&mut row_sq_accum,
&mut shifted,
kernel_perf,
track_kernel_perf,
);
gather_by_index(z, &free_indices, &mut z_free);
p_free.copy_from_slice(&z_free);
if zts <= EPS || !zts.is_finite() {
return Ok(1);
}
let mut k = 1usize;
let log_cgnr_progress = max_iters >= 1024 && x.len() > 1_000_000;
p.fill(0.0);
loop {
scatter_by_index(&p_free, &free_indices, p);
let denom = profiled_forward_band_with_norm(
p,
w,
band,
row_sums,
&mut row_sq_accum,
&mut shifted,
kernel_perf,
track_kernel_perf,
);
if denom <= EPS || !denom.is_finite() {
break;
}
let alpha = zts / denom;
for (slot, &idx) in free_indices.iter().enumerate() {
x[idx] += alpha * p_free[slot];
}
for i in 0..r.len() {
r[i] -= alpha * w[i];
}
let zts_new = profiled_adjoint_band_with_masked_norm(
r,
z,
active_set,
band,
row_sums,
&mut row_sq_accum,
&mut shifted,
kernel_perf,
track_kernel_perf,
);
gather_by_index(z, &free_indices, &mut z_free);
if zts_new <= EPS || !zts_new.is_finite() {
break;
}
let beta = zts_new / zts;
if log_cgnr_progress && k.is_multiple_of(256) {
info!(
"NNLS CGNR progress: iter={}/{} residual={:.3e} elapsed={:.1}s",
k,
max_iters,
zts_new,
started.elapsed().as_secs_f64()
);
}
if zts_new < tol || k >= max_iters {
break;
}
for i in 0..nfree {
p_free[i] = z_free[i] + beta * p_free[i];
}
zts = zts_new;
k += 1;
if let Some(pl) = progress
&& k.is_multiple_of(n)
{
pl.check_for_cancel()?;
}
if started.elapsed() >= Duration::from_millis(max_time_ms) {
break;
}
}
Ok(k)
}
#[allow(clippy::too_many_arguments)] pub(crate) fn eval_gradient_band(
x: &[f64],
d: &[f64],
gradient: &mut [f64],
residual: &mut [f64],
band: &BandIndex,
row_sums: &mut [f64],
shifted: &mut [f64],
kernel_perf: &mut BandKernelPerf,
track_kernel_perf: bool,
) {
profiled_forward_band(
x,
residual,
band,
row_sums,
shifted,
kernel_perf,
track_kernel_perf,
);
residual
.par_iter_mut()
.zip(d.par_iter())
.for_each(|(ri, &di)| *ri -= di);
profiled_adjoint_band(
residual,
gradient,
band,
row_sums,
shifted,
kernel_perf,
track_kernel_perf,
);
}
#[doc(hidden)]
pub fn sum_array_squared_band(x: &[f64], band: &BandIndex, tri_idx: &PackedTriIndex) -> f64 {
let mut total = 0.0f64;
for i in 1..=tri_idx.n {
let start = tri_idx.row_offsets[i];
let len = tri_idx.n - i;
let mut s_i = 0.0f64;
for row_idx in start..start + len {
let val = x[band.row_to_band_map[row_idx]];
s_i += val * val;
}
total += s_i;
}
total
}
#[inline]
fn profiled_forward_band(
x: &[f64],
y: &mut [f64],
band: &BandIndex,
row_sums: &mut [f64],
shifted: &mut [f64],
perf: &mut BandKernelPerf,
track: bool,
) {
if track {
let t0 = Instant::now();
calculate_forward_band(x, y, band, row_sums, shifted);
perf.calc_ab_calls += 1;
perf.calc_ab_time += t0.elapsed();
} else {
calculate_forward_band(x, y, band, row_sums, shifted);
}
}
#[inline]
fn profiled_adjoint_band(
x: &[f64],
y: &mut [f64],
band: &BandIndex,
row_sums: &mut [f64],
shifted: &mut [f64],
perf: &mut BandKernelPerf,
track: bool,
) {
if track {
let t0 = Instant::now();
calculate_ab_band(x, y, band, row_sums, shifted);
perf.calc_atx_calls += 1;
perf.calc_atx_time += t0.elapsed();
} else {
calculate_ab_band(x, y, band, row_sums, shifted);
}
}
#[inline]
#[allow(clippy::too_many_arguments)] fn profiled_forward_band_with_norm(
x: &[f64],
y: &mut [f64],
band: &BandIndex,
row_sums: &mut [f64],
row_sq_accum: &mut [f64],
shifted: &mut [f64],
perf: &mut BandKernelPerf,
track: bool,
) -> f64 {
if track {
let t0 = Instant::now();
let norm = calculate_forward_band_with_norm_sq(x, y, band, row_sums, row_sq_accum, shifted);
perf.calc_ab_calls += 1;
perf.calc_ab_time += t0.elapsed();
norm
} else {
calculate_forward_band_with_norm_sq(x, y, band, row_sums, row_sq_accum, shifted)
}
}
#[inline]
#[allow(clippy::too_many_arguments)] fn profiled_adjoint_band_with_masked_norm(
x: &[f64],
y: &mut [f64],
active_set: &[bool],
band: &BandIndex,
row_sums: &mut [f64],
row_sq_accum: &mut [f64],
shifted: &mut [f64],
perf: &mut BandKernelPerf,
track: bool,
) -> f64 {
if track {
let t0 = Instant::now();
let norm = calculate_ab_band_with_masked_norm_sq(
x,
y,
band,
row_sums,
row_sq_accum,
active_set,
shifted,
);
perf.calc_atx_calls += 1;
perf.calc_atx_time += t0.elapsed();
norm
} else {
calculate_ab_band_with_masked_norm_sq(
x,
y,
band,
row_sums,
row_sq_accum,
active_set,
shifted,
)
}
}
#[inline]
fn collect_free_indices(active_set: &[bool]) -> Vec<usize> {
active_set
.iter()
.enumerate()
.filter_map(|(i, &is_active)| (!is_active).then_some(i))
.collect()
}
#[inline]
fn gather_by_index(src: &[f64], indices: &[usize], dst: &mut [f64]) {
debug_assert_eq!(indices.len(), dst.len());
for (slot, &idx) in indices.iter().enumerate() {
dst[slot] = src[idx];
}
}
#[inline]
fn scatter_by_index(src: &[f64], indices: &[usize], dst: &mut [f64]) {
debug_assert_eq!(indices.len(), src.len());
for (slot, &idx) in indices.iter().enumerate() {
dst[idx] = src[slot];
}
}