1use crate::{error::RegressionResult, num::Float, Array1, Array2};
4use ndarray::ArrayViewMut1;
5use ndarray_linalg::SolveH;
6
7pub(crate) trait IrlsReg<F>
9where
10 F: Float,
11{
12 fn likelihood(&self, regressors: &Array1<F>) -> F;
16
17 fn gradient(&self, l: Array1<F>, regressors: &Array1<F>) -> Array1<F>;
20
21 fn prepare(&mut self, _guess: &Array1<F>) {}
23
24 fn irls_like(&self, regressors: &Array1<F>) -> F {
27 self.likelihood(regressors)
28 }
29
30 fn irls_vec(&self, vec: Array1<F>, regressors: &Array1<F>) -> Array1<F>;
35
36 fn irls_mat(&self, mat: Array2<F>, regressors: &Array1<F>) -> Array2<F>;
40
41 fn next_guess(
44 &mut self,
45 guess: &Array1<F>,
46 irls_vec: Array1<F>,
47 irls_mat: Array2<F>,
48 ) -> RegressionResult<Array1<F>> {
49 self.prepare(guess);
50 let lhs = self.irls_mat(irls_mat, guess);
52 let rhs = self.irls_vec(irls_vec, guess);
54 let next_guess = lhs.solveh_into(rhs)?;
55 Ok(next_guess)
56 }
57
58 fn terminate_ok(&self, _tol: F) -> bool {
59 true
60 }
61}
62
63pub struct Null {}
65
66impl<F: Float> IrlsReg<F> for Null {
67 #[inline]
68 fn likelihood(&self, _: &Array1<F>) -> F {
69 F::zero()
70 }
71 #[inline]
72 fn gradient(&self, jac: Array1<F>, _: &Array1<F>) -> Array1<F> {
73 jac
74 }
75 #[inline]
76 fn irls_vec(&self, vec: Array1<F>, _: &Array1<F>) -> Array1<F> {
77 vec
78 }
79 #[inline]
80 fn irls_mat(&self, mat: Array2<F>, _: &Array1<F>) -> Array2<F> {
81 mat
82 }
83}
84
85pub struct Ridge<F: Float> {
87 l2_vec: Array1<F>,
88}
89
90impl<F: Float> Ridge<F> {
91 pub fn from_diag(l2: Array1<F>) -> Self {
95 Self { l2_vec: l2 }
96 }
97}
98
99impl<F: Float> IrlsReg<F> for Ridge<F> {
100 fn likelihood(&self, beta: &Array1<F>) -> F {
102 -F::from(0.5).unwrap() * (&self.l2_vec * &beta.mapv(|b| b * b)).sum()
103 }
104 fn gradient(&self, jac: Array1<F>, beta: &Array1<F>) -> Array1<F> {
106 jac - (&self.l2_vec * beta)
107 }
108 #[inline]
111 fn irls_vec(&self, vec: Array1<F>, _: &Array1<F>) -> Array1<F> {
112 vec
113 }
114 fn irls_mat(&self, mut mat: Array2<F>, _: &Array1<F>) -> Array2<F> {
116 let mut mat_diag: ArrayViewMut1<F> = mat.diag_mut();
117 mat_diag += &self.l2_vec;
118 mat
119 }
120}
121
122pub struct Lasso<F: Float> {
124 l1_vec: Array1<F>,
126 dual: Array1<F>,
128 cum_res: Array1<F>,
130 rho: F,
132 r_sq: F,
134 s_sq: F,
136}
137
138impl<F: Float> Lasso<F> {
139 pub fn from_diag(l1: Array1<F>) -> Self {
142 let n: usize = l1.len();
143 let gamma = Array1::zeros(n);
144 let u = Array1::zeros(n);
145 Self {
146 l1_vec: l1,
147 dual: gamma,
148 cum_res: u,
149 rho: F::one(),
150 r_sq: F::infinity(), s_sq: F::infinity(),
152 }
153 }
154
155 fn update_rho(&mut self) {
156 let mu: F = F::from(8.).unwrap();
158 let tau: F = F::from(2.).unwrap();
159 if self.r_sq > mu * mu * self.s_sq {
160 self.rho *= tau;
161 self.cum_res /= tau;
162 }
163 if self.r_sq * mu * mu < self.s_sq {
164 self.rho /= tau;
165 self.cum_res *= tau;
166 }
167 }
168}
169
170impl<F: Float> IrlsReg<F> for Lasso<F> {
171 fn likelihood(&self, beta: &Array1<F>) -> F {
172 -(&self.l1_vec * beta.mapv(num_traits::Float::abs)).sum()
173 }
174
175 fn gradient(&self, jac: Array1<F>, regressors: &Array1<F>) -> Array1<F> {
178 jac - &self.l1_vec * ®ressors.mapv(F::sign)
179 }
180
181 fn prepare(&mut self, beta: &Array1<F>) {
183 self.update_rho();
185
186 let old_dual = self.dual.clone();
187
188 self.dual = soft_thresh(beta + &self.cum_res, &self.l1_vec / self.rho);
189 let r: Array1<F> = beta - &self.dual;
191 let s: Array1<F> = (&self.dual - old_dual) * self.rho;
193 self.cum_res += &r;
194
195 self.r_sq = r.mapv(|r| r * r).sum();
196 self.s_sq = s.mapv(|s| s * s).sum();
197 }
198
199 fn irls_like(&self, regressors: &Array1<F>) -> F {
200 -F::from(0.5).unwrap()
201 * self.rho
202 * (regressors - &self.dual + &self.cum_res)
203 .mapv(|x| x * x)
204 .sum()
205 }
206
207 fn irls_vec(&self, vec: Array1<F>, _regressors: &Array1<F>) -> Array1<F> {
210 let d: Array1<F> = &self.dual - &self.cum_res;
211 vec + d * self.rho
212 }
213
214 fn irls_mat(&self, mut mat: Array2<F>, _: &Array1<F>) -> Array2<F> {
216 let mut mat_diag: ArrayViewMut1<F> = mat.diag_mut();
217 mat_diag += self.rho;
218 mat
219 }
220
221 fn terminate_ok(&self, tol: F) -> bool {
222 let n: usize = self.dual.len();
224 let n_sq = F::from((n as f64).sqrt()).unwrap();
225 let r_pass = self.r_sq < n_sq * tol;
226 let s_pass = self.s_sq < n_sq * tol;
227 r_pass && s_pass
228 }
229}
230
231pub struct ElasticNet<F: Float> {
233 l1_vec: Array1<F>,
235 l2_vec: Array1<F>,
237 dual: Array1<F>,
239 cum_res: Array1<F>,
241 rho: F,
243 r_sq: F,
245 s_sq: F,
247}
248
249impl<F: Float> ElasticNet<F> {
250 pub fn from_diag(l1: Array1<F>, l2: Array1<F>) -> Self {
253 let n: usize = l1.len();
254 let gamma = Array1::zeros(n);
255 let u = Array1::zeros(n);
256 Self {
257 l1_vec: l1,
258 l2_vec: l2,
259 dual: gamma,
260 cum_res: u,
261 rho: F::one(),
262 r_sq: F::infinity(), s_sq: F::infinity(),
264 }
265 }
266
267 fn update_rho(&mut self) {
268 let mu: F = F::from(8.).unwrap();
270 let tau: F = F::from(2.).unwrap();
271 if self.r_sq > mu * mu * self.s_sq {
272 self.rho *= tau;
273 self.cum_res /= tau;
274 }
275 if self.r_sq * mu * mu < self.s_sq {
276 self.rho /= tau;
277 self.cum_res *= tau;
278 }
279 }
280}
281
282impl<F: Float> IrlsReg<F> for ElasticNet<F> {
283 fn likelihood(&self, beta: &Array1<F>) -> F {
284 -(&self.l1_vec * beta.mapv(num_traits::Float::abs)).sum()
285 -F::from(0.5).unwrap() * (&self.l2_vec * &beta.mapv(|b| b * b)).sum()
286 }
287
288 fn gradient(&self, jac: Array1<F>, regressors: &Array1<F>) -> Array1<F> {
291 jac - &self.l1_vec * ®ressors.mapv(F::sign) - &self.l2_vec * regressors
292 }
293
294 fn prepare(&mut self, beta: &Array1<F>) {
296 self.update_rho();
298
299 let old_dual = self.dual.clone();
300
301 self.dual = soft_thresh(beta + &self.cum_res, &self.l1_vec / self.rho);
302 let r: Array1<F> = beta - &self.dual;
304 let s: Array1<F> = (&self.dual - old_dual) * self.rho;
306 self.cum_res += &r;
307
308 self.r_sq = r.mapv(|r| r * r).sum();
309 self.s_sq = s.mapv(|s| s * s).sum();
310 }
311
312 fn irls_like(&self, regressors: &Array1<F>) -> F {
313 -F::from(0.5).unwrap()
314 * self.rho
315 * (regressors - &self.dual + &self.cum_res)
316 .mapv(|x| x * x)
317 .sum()
318 -F::from(0.5).unwrap() * (&self.l2_vec * ®ressors.mapv(|b| b * b)).sum()
319 }
320
321 fn irls_vec(&self, vec: Array1<F>, _regressors: &Array1<F>) -> Array1<F> {
324 let d: Array1<F> = &self.dual - &self.cum_res;
325 vec + d * self.rho
326 }
327
328 fn irls_mat(&self, mut mat: Array2<F>, _: &Array1<F>) -> Array2<F> {
330 let mut mat_diag: ArrayViewMut1<F> = mat.diag_mut();
331 mat_diag += &self.l2_vec;
332 mat_diag += self.rho;
333 mat
334 }
335
336 fn terminate_ok(&self, tol: F) -> bool {
337 let n: usize = self.dual.len();
339 let n_sq = F::from((n as f64).sqrt()).unwrap();
340 let r_pass = self.r_sq < n_sq * tol;
341 let s_pass = self.s_sq < n_sq * tol;
342 r_pass && s_pass
343 }
344}
345
346fn soft_thresh<F: Float>(x: Array1<F>, lambda: Array1<F>) -> Array1<F> {
348 let sign_x = x.mapv(F::sign);
349 let abs_x = x.mapv(<F as num_traits::Float>::abs);
350 let red_x = abs_x - lambda;
351 let clipped = red_x.mapv(|x| if x < F::zero() { F::zero() } else { x });
352 sign_x * clipped
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358 use approx::assert_abs_diff_eq;
359 use ndarray::array;
360
361 #[test]
362 fn ridge_matrix() {
363 let l = 1e-4;
364 let ridge = Ridge::from_diag(array![0., l]);
365 let mat = array![[0.5, 0.1], [0.1, 0.2]];
366 let mut target_mat = mat.clone();
367 target_mat[[1, 1]] += l;
368 let dummy_beta = array![0., 0.];
369 assert_eq!(ridge.irls_mat(mat, &dummy_beta), target_mat);
370 }
371
372 #[test]
373 fn soft_thresh_correct() {
374 let x = array![0.25, -0.1, -0.4, 0.3, 0.5, -0.5];
375 let lambda = array![-0., 0.0, 0.1, 0.1, 1.0, 1.0];
376 let target = array![0.25, -0.1, -0.3, 0.2, 0., 0.];
377 let output = soft_thresh(x, lambda);
378 assert_abs_diff_eq!(target, output);
379 }
380}