1use crate::qmatrix::QMatrix;
10
11const TAU: f64 = 1e-12;
12const INF: f64 = f64::INFINITY;
13
14#[derive(Debug, Clone)]
16pub struct SolutionInfo {
17 pub obj: f64,
19 pub rho: f64,
21 pub upper_bound_p: f64,
23 pub upper_bound_n: f64,
25 pub r: f64,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31enum AlphaStatus {
32 LowerBound,
33 UpperBound,
34 Free,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum SolverVariant {
40 Standard,
42 Nu,
44}
45
46pub struct Solver<'a> {
48 l: usize,
49 active_size: usize,
50 variant: SolverVariant,
51
52 y: Vec<i8>,
53 g: Vec<f64>,
54 g_bar: Vec<f64>,
55 alpha: Vec<f64>,
56 alpha_status: Vec<AlphaStatus>,
57 p: Vec<f64>,
58 active_set: Vec<usize>,
59 unshrink: bool,
60
61 q: Box<dyn QMatrix + 'a>,
62 qd: Vec<f64>,
63 cp: f64,
64 cn: f64,
65 eps: f64,
66}
67
68#[allow(clippy::needless_range_loop)]
69impl<'a> Solver<'a> {
70 #[allow(clippy::too_many_arguments)]
83 pub fn solve(
84 variant: SolverVariant,
85 l: usize,
86 q: Box<dyn QMatrix + 'a>,
87 p_: &[f64],
88 y_: &[i8],
89 alpha_: &mut [f64],
90 cp: f64,
91 cn: f64,
92 eps: f64,
93 shrinking: bool,
94 ) -> SolutionInfo {
95 let qd = q.get_qd().to_vec();
96 let p = p_.to_vec();
97 let y = y_.to_vec();
98 let alpha = alpha_.to_vec();
99
100 let mut solver = Solver {
101 l,
102 active_size: l,
103 variant,
104 y,
105 g: vec![0.0; l],
106 g_bar: vec![0.0; l],
107 alpha,
108 alpha_status: vec![AlphaStatus::LowerBound; l],
109 p,
110 active_set: (0..l).collect(),
111 unshrink: false,
112 q,
113 qd,
114 cp,
115 cn,
116 eps,
117 };
118
119 for i in 0..l {
121 solver.update_alpha_status(i);
122 }
123
124 for i in 0..l {
126 solver.g[i] = solver.p[i];
127 }
128
129 for i in 0..l {
130 if !solver.is_lower_bound(i) {
131 let alpha_i = solver.alpha[i];
132 let q_i = solver.q.get_q(i, l).to_vec();
133 for j in 0..l {
134 solver.g[j] += alpha_i * q_i[j] as f64;
135 }
136 if solver.is_upper_bound(i) {
137 let c_i = solver.get_c(i);
138 for j in 0..l {
139 solver.g_bar[j] += c_i * q_i[j] as f64;
140 }
141 }
142 }
143 }
144
145 let max_iter = 10_000_000usize.max(if l > i32::MAX as usize / 100 {
147 usize::MAX
148 } else {
149 100 * l
150 });
151 let mut counter = l.min(1000) + 1;
152 let mut iter = 0usize;
153
154 while iter < max_iter {
155 counter -= 1;
157 if counter == 0 {
158 counter = l.min(1000);
159 if shrinking {
160 solver.do_shrinking();
161 }
162 }
163
164 let (wi, wj) = match solver.select_working_set() {
165 Some(pair) => pair,
166 None => {
167 solver.reconstruct_gradient();
169 solver.active_size = l;
170 match solver.select_working_set() {
171 Some(pair) => {
172 counter = 1; pair
174 }
175 None => break, }
177 }
178 };
179
180 iter += 1;
181
182 solver.update_alpha_pair(wi, wj);
184 }
185
186 if iter >= max_iter {
187 if solver.active_size < l {
188 solver.reconstruct_gradient();
189 solver.active_size = l;
190 }
191 crate::info("WARNING: reaching max number of iterations\n");
192 }
193
194 let (rho, r) = solver.calculate_rho();
196
197 let obj = {
199 let mut v = 0.0;
200 for i in 0..l {
201 v += solver.alpha[i] * (solver.g[i] + solver.p[i]);
202 }
203 v / 2.0
204 };
205
206 for i in 0..l {
208 alpha_[solver.active_set[i]] = solver.alpha[i];
209 }
210
211 let si = SolutionInfo {
212 obj,
213 rho,
214 upper_bound_p: cp,
215 upper_bound_n: cn,
216 r,
217 };
218
219 crate::info(&format!("optimization finished, #iter = {}\n", iter));
220
221 si
222 }
223
224 #[inline]
227 fn get_c(&self, i: usize) -> f64 {
228 if self.y[i] > 0 {
229 self.cp
230 } else {
231 self.cn
232 }
233 }
234
235 #[inline]
236 fn update_alpha_status(&mut self, i: usize) {
237 self.alpha_status[i] = if self.alpha[i] >= self.get_c(i) {
238 AlphaStatus::UpperBound
239 } else if self.alpha[i] <= 0.0 {
240 AlphaStatus::LowerBound
241 } else {
242 AlphaStatus::Free
243 };
244 }
245
246 #[inline]
247 fn is_upper_bound(&self, i: usize) -> bool {
248 self.alpha_status[i] == AlphaStatus::UpperBound
249 }
250
251 #[inline]
252 fn is_lower_bound(&self, i: usize) -> bool {
253 self.alpha_status[i] == AlphaStatus::LowerBound
254 }
255
256 #[inline]
257 fn is_free(&self, i: usize) -> bool {
258 self.alpha_status[i] == AlphaStatus::Free
259 }
260
261 fn swap_index(&mut self, i: usize, j: usize) {
262 self.q.swap_index(i, j);
263 self.y.swap(i, j);
264 self.g.swap(i, j);
265 self.alpha_status.swap(i, j);
266 self.alpha.swap(i, j);
267 self.p.swap(i, j);
268 self.active_set.swap(i, j);
269 self.g_bar.swap(i, j);
270 self.qd.swap(i, j);
271 }
272
273 fn reconstruct_gradient(&mut self) {
274 if self.active_size == self.l {
275 return;
276 }
277
278 for j in self.active_size..self.l {
279 self.g[j] = self.g_bar[j] + self.p[j];
280 }
281
282 let mut nr_free = 0;
283 for j in 0..self.active_size {
284 if self.is_free(j) {
285 nr_free += 1;
286 }
287 }
288
289 if 2 * nr_free < self.active_size {
290 crate::info("WARNING: using -h 0 may be faster\n");
291 }
292
293 let active_size = self.active_size;
294 let l = self.l;
295
296 if nr_free * l > 2 * active_size * (l - active_size) {
297 for i in active_size..l {
298 let q_i = self.q.get_q(i, active_size).to_vec();
299 for j in 0..active_size {
300 if self.is_free(j) {
301 self.g[i] += self.alpha[j] * q_i[j] as f64;
302 }
303 }
304 }
305 } else {
306 for i in 0..active_size {
307 if self.is_free(i) {
308 let q_i = self.q.get_q(i, l).to_vec();
309 let alpha_i = self.alpha[i];
310 for j in active_size..l {
311 self.g[j] += alpha_i * q_i[j] as f64;
312 }
313 }
314 }
315 }
316 }
317
318 fn select_working_set(&mut self) -> Option<(usize, usize)> {
322 match self.variant {
323 SolverVariant::Standard => self.select_working_set_standard(),
324 SolverVariant::Nu => self.select_working_set_nu(),
325 }
326 }
327
328 fn select_working_set_standard(&mut self) -> Option<(usize, usize)> {
329 let mut gmax = -INF;
330 let mut gmax2 = -INF;
331 let mut gmax_idx: Option<usize> = None;
332 let mut gmin_idx: Option<usize> = None;
333 let mut obj_diff_min = INF;
334
335 let n = self.active_size;
338 let (y, g, status) = (&self.y[..n], &self.g[..n], &self.alpha_status[..n]);
339 for t in 0..n {
340 if y[t] == 1 {
341 if status[t] != AlphaStatus::UpperBound && -g[t] >= gmax {
342 gmax = -g[t];
343 gmax_idx = Some(t);
344 }
345 } else if status[t] != AlphaStatus::LowerBound && g[t] >= gmax {
346 gmax = g[t];
347 gmax_idx = Some(t);
348 }
349 }
350
351 let i = gmax_idx?;
352 let q_i = self.q.get_q(i, self.active_size).to_vec();
353
354 let qd_i = self.qd[i];
357 let yi = y[i] as f64;
358 for j in 0..n {
359 if y[j] == 1 {
360 if status[j] != AlphaStatus::LowerBound {
361 let grad_diff = gmax + g[j];
362 if g[j] >= gmax2 {
363 gmax2 = g[j];
364 }
365 if grad_diff > 0.0 {
366 let quad_coef = qd_i + self.qd[j] - 2.0 * yi * q_i[j] as f64;
367 let obj_diff = if quad_coef > 0.0 {
368 -(grad_diff * grad_diff) / quad_coef
369 } else {
370 -(grad_diff * grad_diff) / TAU
371 };
372 if obj_diff <= obj_diff_min {
373 gmin_idx = Some(j);
374 obj_diff_min = obj_diff;
375 }
376 }
377 }
378 } else if status[j] != AlphaStatus::UpperBound {
379 let grad_diff = gmax - g[j];
380 if -g[j] >= gmax2 {
381 gmax2 = -g[j];
382 }
383 if grad_diff > 0.0 {
384 let quad_coef = qd_i + self.qd[j] + 2.0 * yi * q_i[j] as f64;
385 let obj_diff = if quad_coef > 0.0 {
386 -(grad_diff * grad_diff) / quad_coef
387 } else {
388 -(grad_diff * grad_diff) / TAU
389 };
390 if obj_diff <= obj_diff_min {
391 gmin_idx = Some(j);
392 obj_diff_min = obj_diff;
393 }
394 }
395 }
396 }
397
398 if gmax + gmax2 < self.eps || gmin_idx.is_none() {
399 return None;
400 }
401
402 Some((i, gmin_idx?))
403 }
404
405 fn select_working_set_nu(&mut self) -> Option<(usize, usize)> {
406 let mut gmaxp = -INF;
407 let mut gmaxp2 = -INF;
408 let mut gmaxp_idx: Option<usize> = None;
409 let mut gmaxn = -INF;
410 let mut gmaxn2 = -INF;
411 let mut gmaxn_idx: Option<usize> = None;
412 let mut gmin_idx: Option<usize> = None;
413 let mut obj_diff_min = INF;
414
415 let n = self.active_size;
417 let (y, g, status) = (&self.y[..n], &self.g[..n], &self.alpha_status[..n]);
418 for t in 0..n {
419 if y[t] == 1 {
420 if status[t] != AlphaStatus::UpperBound && -g[t] >= gmaxp {
421 gmaxp = -g[t];
422 gmaxp_idx = Some(t);
423 }
424 } else if status[t] != AlphaStatus::LowerBound && g[t] >= gmaxn {
425 gmaxn = g[t];
426 gmaxn_idx = Some(t);
427 }
428 }
429
430 let ip = gmaxp_idx;
431 let in_ = gmaxn_idx;
432
433 let q_ip = if let Some(ip) = ip {
434 Some(self.q.get_q(ip, self.active_size).to_vec())
435 } else {
436 None
437 };
438 let q_in = if let Some(in_) = in_ {
439 Some(self.q.get_q(in_, self.active_size).to_vec())
440 } else {
441 None
442 };
443
444 for j in 0..n {
445 if y[j] == 1 {
446 if status[j] != AlphaStatus::LowerBound {
447 let grad_diff = gmaxp + g[j];
448 if g[j] >= gmaxp2 {
449 gmaxp2 = g[j];
450 }
451 if grad_diff > 0.0 {
452 if let (Some(ip), Some(q_ip)) = (ip, q_ip.as_ref()) {
453 let quad_coef = self.qd[ip] + self.qd[j] - 2.0 * q_ip[j] as f64;
454 let obj_diff = if quad_coef > 0.0 {
455 -(grad_diff * grad_diff) / quad_coef
456 } else {
457 -(grad_diff * grad_diff) / TAU
458 };
459 if obj_diff <= obj_diff_min {
460 gmin_idx = Some(j);
461 obj_diff_min = obj_diff;
462 }
463 }
464 }
465 }
466 } else if status[j] != AlphaStatus::UpperBound {
467 let grad_diff = gmaxn - g[j];
468 if -g[j] >= gmaxn2 {
469 gmaxn2 = -g[j];
470 }
471 if grad_diff > 0.0 {
472 if let (Some(in_), Some(q_in)) = (in_, q_in.as_ref()) {
473 let quad_coef = self.qd[in_] + self.qd[j] - 2.0 * q_in[j] as f64;
474 let obj_diff = if quad_coef > 0.0 {
475 -(grad_diff * grad_diff) / quad_coef
476 } else {
477 -(grad_diff * grad_diff) / TAU
478 };
479 if obj_diff <= obj_diff_min {
480 gmin_idx = Some(j);
481 obj_diff_min = obj_diff;
482 }
483 }
484 }
485 }
486 }
487
488 if f64::max(gmaxp + gmaxp2, gmaxn + gmaxn2) < self.eps || gmin_idx.is_none() {
489 return None;
490 }
491
492 let out_j = gmin_idx?;
493 let out_i = if self.y[out_j] == 1 {
494 gmaxp_idx?
495 } else {
496 gmaxn_idx?
497 };
498
499 Some((out_i, out_j))
500 }
501
502 fn update_alpha_pair(&mut self, i: usize, j: usize) {
505 let active_size = self.active_size;
506 let q_i = self.q.get_q(i, active_size).to_vec();
507 let q_j = self.q.get_q(j, active_size).to_vec();
508
509 let c_i = self.get_c(i);
510 let c_j = self.get_c(j);
511
512 let old_alpha_i = self.alpha[i];
513 let old_alpha_j = self.alpha[j];
514
515 if self.y[i] != self.y[j] {
516 let mut quad_coef = self.qd[i] + self.qd[j] + 2.0 * q_i[j] as f64;
517 if quad_coef <= 0.0 {
518 quad_coef = TAU;
519 }
520 let delta = (-self.g[i] - self.g[j]) / quad_coef;
521 let diff = self.alpha[i] - self.alpha[j];
522 self.alpha[i] += delta;
523 self.alpha[j] += delta;
524
525 if diff > 0.0 {
526 if self.alpha[j] < 0.0 {
527 self.alpha[j] = 0.0;
528 self.alpha[i] = diff;
529 }
530 } else if self.alpha[i] < 0.0 {
531 self.alpha[i] = 0.0;
532 self.alpha[j] = -diff;
533 }
534 if diff > c_i - c_j {
535 if self.alpha[i] > c_i {
536 self.alpha[i] = c_i;
537 self.alpha[j] = c_i - diff;
538 }
539 } else if self.alpha[j] > c_j {
540 self.alpha[j] = c_j;
541 self.alpha[i] = c_j + diff;
542 }
543 } else {
544 let mut quad_coef = self.qd[i] + self.qd[j] - 2.0 * q_i[j] as f64;
545 if quad_coef <= 0.0 {
546 quad_coef = TAU;
547 }
548 let delta = (self.g[i] - self.g[j]) / quad_coef;
549 let sum = self.alpha[i] + self.alpha[j];
550 self.alpha[i] -= delta;
551 self.alpha[j] += delta;
552
553 if sum > c_i {
554 if self.alpha[i] > c_i {
555 self.alpha[i] = c_i;
556 self.alpha[j] = sum - c_i;
557 }
558 } else if self.alpha[j] < 0.0 {
559 self.alpha[j] = 0.0;
560 self.alpha[i] = sum;
561 }
562 if sum > c_j {
563 if self.alpha[j] > c_j {
564 self.alpha[j] = c_j;
565 self.alpha[i] = sum - c_j;
566 }
567 } else if self.alpha[i] < 0.0 {
568 self.alpha[i] = 0.0;
569 self.alpha[j] = sum;
570 }
571 }
572
573 let delta_alpha_i = self.alpha[i] - old_alpha_i;
575 let delta_alpha_j = self.alpha[j] - old_alpha_j;
576
577 let g = &mut self.g[..active_size];
579 for k in 0..active_size {
580 g[k] += q_i[k] as f64 * delta_alpha_i + q_j[k] as f64 * delta_alpha_j;
581 }
582
583 let ui = self.is_upper_bound(i);
585 let uj = self.is_upper_bound(j);
586 self.update_alpha_status(i);
587 self.update_alpha_status(j);
588
589 let l = self.l;
590
591 if ui != self.is_upper_bound(i) {
592 let q_i_full = self.q.get_q(i, l).to_vec();
593 if ui {
594 for k in 0..l {
595 self.g_bar[k] -= c_i * q_i_full[k] as f64;
596 }
597 } else {
598 for k in 0..l {
599 self.g_bar[k] += c_i * q_i_full[k] as f64;
600 }
601 }
602 }
603
604 if uj != self.is_upper_bound(j) {
605 let q_j_full = self.q.get_q(j, l).to_vec();
606 if uj {
607 for k in 0..l {
608 self.g_bar[k] -= c_j * q_j_full[k] as f64;
609 }
610 } else {
611 for k in 0..l {
612 self.g_bar[k] += c_j * q_j_full[k] as f64;
613 }
614 }
615 }
616 }
617
618 fn do_shrinking(&mut self) {
621 match self.variant {
622 SolverVariant::Standard => self.do_shrinking_standard(),
623 SolverVariant::Nu => self.do_shrinking_nu(),
624 }
625 }
626
627 fn be_shrunk_standard(&self, i: usize, gmax1: f64, gmax2: f64) -> bool {
628 if self.is_upper_bound(i) {
629 if self.y[i] == 1 {
630 -self.g[i] > gmax1
631 } else {
632 -self.g[i] > gmax2
633 }
634 } else if self.is_lower_bound(i) {
635 if self.y[i] == 1 {
636 self.g[i] > gmax2
637 } else {
638 self.g[i] > gmax1
639 }
640 } else {
641 false
642 }
643 }
644
645 fn do_shrinking_standard(&mut self) {
646 let mut gmax1 = -INF;
647 let mut gmax2 = -INF;
648
649 for i in 0..self.active_size {
650 if self.y[i] == 1 {
651 if !self.is_upper_bound(i) && -self.g[i] >= gmax1 {
652 gmax1 = -self.g[i];
653 }
654 if !self.is_lower_bound(i) && self.g[i] >= gmax2 {
655 gmax2 = self.g[i];
656 }
657 } else {
658 if !self.is_upper_bound(i) && -self.g[i] >= gmax2 {
659 gmax2 = -self.g[i];
660 }
661 if !self.is_lower_bound(i) && self.g[i] >= gmax1 {
662 gmax1 = self.g[i];
663 }
664 }
665 }
666
667 if !self.unshrink && gmax1 + gmax2 <= self.eps * 10.0 {
668 self.unshrink = true;
669 self.reconstruct_gradient();
670 self.active_size = self.l;
671 }
672
673 let mut i = 0;
674 while i < self.active_size {
675 if self.be_shrunk_standard(i, gmax1, gmax2) {
676 self.active_size -= 1;
677 while self.active_size > i {
678 if !self.be_shrunk_standard(self.active_size, gmax1, gmax2) {
679 self.swap_index(i, self.active_size);
680 break;
681 }
682 self.active_size -= 1;
683 }
684 }
685 i += 1;
686 }
687 }
688
689 fn be_shrunk_nu(&self, i: usize, gmax1: f64, gmax2: f64, gmax3: f64, gmax4: f64) -> bool {
690 if self.is_upper_bound(i) {
691 if self.y[i] == 1 {
692 -self.g[i] > gmax1
693 } else {
694 -self.g[i] > gmax4
695 }
696 } else if self.is_lower_bound(i) {
697 if self.y[i] == 1 {
698 self.g[i] > gmax2
699 } else {
700 self.g[i] > gmax3
701 }
702 } else {
703 false
704 }
705 }
706
707 fn do_shrinking_nu(&mut self) {
708 let mut gmax1 = -INF;
709 let mut gmax2 = -INF;
710 let mut gmax3 = -INF;
711 let mut gmax4 = -INF;
712
713 for i in 0..self.active_size {
714 if !self.is_upper_bound(i) {
715 if self.y[i] == 1 {
716 if -self.g[i] > gmax1 {
717 gmax1 = -self.g[i];
718 }
719 } else if -self.g[i] > gmax4 {
720 gmax4 = -self.g[i];
721 }
722 }
723 if !self.is_lower_bound(i) {
724 if self.y[i] == 1 {
725 if self.g[i] > gmax2 {
726 gmax2 = self.g[i];
727 }
728 } else if self.g[i] > gmax3 {
729 gmax3 = self.g[i];
730 }
731 }
732 }
733
734 if !self.unshrink && f64::max(gmax1 + gmax2, gmax3 + gmax4) <= self.eps * 10.0 {
735 self.unshrink = true;
736 self.reconstruct_gradient();
737 self.active_size = self.l;
738 }
739
740 let mut i = 0;
741 while i < self.active_size {
742 if self.be_shrunk_nu(i, gmax1, gmax2, gmax3, gmax4) {
743 self.active_size -= 1;
744 while self.active_size > i {
745 if !self.be_shrunk_nu(self.active_size, gmax1, gmax2, gmax3, gmax4) {
746 self.swap_index(i, self.active_size);
747 break;
748 }
749 self.active_size -= 1;
750 }
751 }
752 i += 1;
753 }
754 }
755
756 fn calculate_rho(&self) -> (f64, f64) {
759 match self.variant {
760 SolverVariant::Standard => (self.calculate_rho_standard(), 0.0),
761 SolverVariant::Nu => self.calculate_rho_nu(),
762 }
763 }
764
765 fn calculate_rho_standard(&self) -> f64 {
766 let mut nr_free = 0;
767 let mut ub = INF;
768 let mut lb = -INF;
769 let mut sum_free = 0.0;
770
771 for i in 0..self.active_size {
772 let yg = self.y[i] as f64 * self.g[i];
773
774 if self.is_upper_bound(i) {
775 if self.y[i] == -1 {
776 ub = ub.min(yg);
777 } else {
778 lb = lb.max(yg);
779 }
780 } else if self.is_lower_bound(i) {
781 if self.y[i] == 1 {
782 ub = ub.min(yg);
783 } else {
784 lb = lb.max(yg);
785 }
786 } else {
787 nr_free += 1;
788 sum_free += yg;
789 }
790 }
791
792 if nr_free > 0 {
793 sum_free / nr_free as f64
794 } else {
795 (ub + lb) / 2.0
796 }
797 }
798
799 fn calculate_rho_nu(&self) -> (f64, f64) {
800 let mut nr_free1 = 0;
801 let mut nr_free2 = 0;
802 let mut ub1 = INF;
803 let mut ub2 = INF;
804 let mut lb1 = -INF;
805 let mut lb2 = -INF;
806 let mut sum_free1 = 0.0;
807 let mut sum_free2 = 0.0;
808
809 for i in 0..self.active_size {
810 if self.y[i] == 1 {
811 if self.is_upper_bound(i) {
812 lb1 = lb1.max(self.g[i]);
813 } else if self.is_lower_bound(i) {
814 ub1 = ub1.min(self.g[i]);
815 } else {
816 nr_free1 += 1;
817 sum_free1 += self.g[i];
818 }
819 } else if self.is_upper_bound(i) {
820 lb2 = lb2.max(self.g[i]);
821 } else if self.is_lower_bound(i) {
822 ub2 = ub2.min(self.g[i]);
823 } else {
824 nr_free2 += 1;
825 sum_free2 += self.g[i];
826 }
827 }
828
829 let r1 = if nr_free1 > 0 {
830 sum_free1 / nr_free1 as f64
831 } else {
832 (ub1 + lb1) / 2.0
833 };
834
835 let r2 = if nr_free2 > 0 {
836 sum_free2 / nr_free2 as f64
837 } else {
838 (ub2 + lb2) / 2.0
839 };
840
841 let rho = (r1 - r2) / 2.0;
842 let r = (r1 + r2) / 2.0;
843 (rho, r)
844 }
845}