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 for t in 0..self.active_size {
337 if self.y[t] == 1 {
338 if !self.is_upper_bound(t) && -self.g[t] >= gmax {
339 gmax = -self.g[t];
340 gmax_idx = Some(t);
341 }
342 } else if !self.is_lower_bound(t) && self.g[t] >= gmax {
343 gmax = self.g[t];
344 gmax_idx = Some(t);
345 }
346 }
347
348 let i = gmax_idx?;
349 let q_i = self.q.get_q(i, self.active_size).to_vec();
350
351 for j in 0..self.active_size {
353 if self.y[j] == 1 {
354 if !self.is_lower_bound(j) {
355 let grad_diff = gmax + self.g[j];
356 if self.g[j] >= gmax2 {
357 gmax2 = self.g[j];
358 }
359 if grad_diff > 0.0 {
360 let quad_coef =
361 self.qd[i] + self.qd[j] - 2.0 * (self.y[i] as f64) * q_i[j] as f64;
362 let obj_diff = if quad_coef > 0.0 {
363 -(grad_diff * grad_diff) / quad_coef
364 } else {
365 -(grad_diff * grad_diff) / TAU
366 };
367 if obj_diff <= obj_diff_min {
368 gmin_idx = Some(j);
369 obj_diff_min = obj_diff;
370 }
371 }
372 }
373 } else if !self.is_upper_bound(j) {
374 let grad_diff = gmax - self.g[j];
375 if -self.g[j] >= gmax2 {
376 gmax2 = -self.g[j];
377 }
378 if grad_diff > 0.0 {
379 let quad_coef =
380 self.qd[i] + self.qd[j] + 2.0 * (self.y[i] as f64) * q_i[j] as f64;
381 let obj_diff = if quad_coef > 0.0 {
382 -(grad_diff * grad_diff) / quad_coef
383 } else {
384 -(grad_diff * grad_diff) / TAU
385 };
386 if obj_diff <= obj_diff_min {
387 gmin_idx = Some(j);
388 obj_diff_min = obj_diff;
389 }
390 }
391 }
392 }
393
394 if gmax + gmax2 < self.eps || gmin_idx.is_none() {
395 return None;
396 }
397
398 Some((i, gmin_idx?))
399 }
400
401 fn select_working_set_nu(&mut self) -> Option<(usize, usize)> {
402 let mut gmaxp = -INF;
403 let mut gmaxp2 = -INF;
404 let mut gmaxp_idx: Option<usize> = None;
405 let mut gmaxn = -INF;
406 let mut gmaxn2 = -INF;
407 let mut gmaxn_idx: Option<usize> = None;
408 let mut gmin_idx: Option<usize> = None;
409 let mut obj_diff_min = INF;
410
411 for t in 0..self.active_size {
412 if self.y[t] == 1 {
413 if !self.is_upper_bound(t) && -self.g[t] >= gmaxp {
414 gmaxp = -self.g[t];
415 gmaxp_idx = Some(t);
416 }
417 } else if !self.is_lower_bound(t) && self.g[t] >= gmaxn {
418 gmaxn = self.g[t];
419 gmaxn_idx = Some(t);
420 }
421 }
422
423 let ip = gmaxp_idx;
424 let in_ = gmaxn_idx;
425
426 let q_ip = if let Some(ip) = ip {
427 Some(self.q.get_q(ip, self.active_size).to_vec())
428 } else {
429 None
430 };
431 let q_in = if let Some(in_) = in_ {
432 Some(self.q.get_q(in_, self.active_size).to_vec())
433 } else {
434 None
435 };
436
437 for j in 0..self.active_size {
438 if self.y[j] == 1 {
439 if !self.is_lower_bound(j) {
440 let grad_diff = gmaxp + self.g[j];
441 if self.g[j] >= gmaxp2 {
442 gmaxp2 = self.g[j];
443 }
444 if grad_diff > 0.0 {
445 if let (Some(ip), Some(ref q_ip)) = (ip, &q_ip) {
446 let quad_coef = self.qd[ip] + self.qd[j] - 2.0 * q_ip[j] as f64;
447 let obj_diff = if quad_coef > 0.0 {
448 -(grad_diff * grad_diff) / quad_coef
449 } else {
450 -(grad_diff * grad_diff) / TAU
451 };
452 if obj_diff <= obj_diff_min {
453 gmin_idx = Some(j);
454 obj_diff_min = obj_diff;
455 }
456 }
457 }
458 }
459 } else if !self.is_upper_bound(j) {
460 let grad_diff = gmaxn - self.g[j];
461 if -self.g[j] >= gmaxn2 {
462 gmaxn2 = -self.g[j];
463 }
464 if grad_diff > 0.0 {
465 if let (Some(in_), Some(ref q_in)) = (in_, &q_in) {
466 let quad_coef = self.qd[in_] + self.qd[j] - 2.0 * q_in[j] as f64;
467 let obj_diff = if quad_coef > 0.0 {
468 -(grad_diff * grad_diff) / quad_coef
469 } else {
470 -(grad_diff * grad_diff) / TAU
471 };
472 if obj_diff <= obj_diff_min {
473 gmin_idx = Some(j);
474 obj_diff_min = obj_diff;
475 }
476 }
477 }
478 }
479 }
480
481 if f64::max(gmaxp + gmaxp2, gmaxn + gmaxn2) < self.eps || gmin_idx.is_none() {
482 return None;
483 }
484
485 let out_j = gmin_idx?;
486 let out_i = if self.y[out_j] == 1 {
487 gmaxp_idx?
488 } else {
489 gmaxn_idx?
490 };
491
492 Some((out_i, out_j))
493 }
494
495 fn update_alpha_pair(&mut self, i: usize, j: usize) {
498 let active_size = self.active_size;
499 let q_i = self.q.get_q(i, active_size).to_vec();
500 let q_j = self.q.get_q(j, active_size).to_vec();
501
502 let c_i = self.get_c(i);
503 let c_j = self.get_c(j);
504
505 let old_alpha_i = self.alpha[i];
506 let old_alpha_j = self.alpha[j];
507
508 if self.y[i] != self.y[j] {
509 let mut quad_coef = self.qd[i] + self.qd[j] + 2.0 * q_i[j] as f64;
510 if quad_coef <= 0.0 {
511 quad_coef = TAU;
512 }
513 let delta = (-self.g[i] - self.g[j]) / quad_coef;
514 let diff = self.alpha[i] - self.alpha[j];
515 self.alpha[i] += delta;
516 self.alpha[j] += delta;
517
518 if diff > 0.0 {
519 if self.alpha[j] < 0.0 {
520 self.alpha[j] = 0.0;
521 self.alpha[i] = diff;
522 }
523 } else if self.alpha[i] < 0.0 {
524 self.alpha[i] = 0.0;
525 self.alpha[j] = -diff;
526 }
527 if diff > c_i - c_j {
528 if self.alpha[i] > c_i {
529 self.alpha[i] = c_i;
530 self.alpha[j] = c_i - diff;
531 }
532 } else if self.alpha[j] > c_j {
533 self.alpha[j] = c_j;
534 self.alpha[i] = c_j + diff;
535 }
536 } else {
537 let mut quad_coef = self.qd[i] + self.qd[j] - 2.0 * q_i[j] as f64;
538 if quad_coef <= 0.0 {
539 quad_coef = TAU;
540 }
541 let delta = (self.g[i] - self.g[j]) / quad_coef;
542 let sum = self.alpha[i] + self.alpha[j];
543 self.alpha[i] -= delta;
544 self.alpha[j] += delta;
545
546 if sum > c_i {
547 if self.alpha[i] > c_i {
548 self.alpha[i] = c_i;
549 self.alpha[j] = sum - c_i;
550 }
551 } else if self.alpha[j] < 0.0 {
552 self.alpha[j] = 0.0;
553 self.alpha[i] = sum;
554 }
555 if sum > c_j {
556 if self.alpha[j] > c_j {
557 self.alpha[j] = c_j;
558 self.alpha[i] = sum - c_j;
559 }
560 } else if self.alpha[i] < 0.0 {
561 self.alpha[i] = 0.0;
562 self.alpha[j] = sum;
563 }
564 }
565
566 let delta_alpha_i = self.alpha[i] - old_alpha_i;
568 let delta_alpha_j = self.alpha[j] - old_alpha_j;
569
570 for k in 0..active_size {
571 self.g[k] += q_i[k] as f64 * delta_alpha_i + q_j[k] as f64 * delta_alpha_j;
572 }
573
574 let ui = self.is_upper_bound(i);
576 let uj = self.is_upper_bound(j);
577 self.update_alpha_status(i);
578 self.update_alpha_status(j);
579
580 let l = self.l;
581
582 if ui != self.is_upper_bound(i) {
583 let q_i_full = self.q.get_q(i, l).to_vec();
584 if ui {
585 for k in 0..l {
586 self.g_bar[k] -= c_i * q_i_full[k] as f64;
587 }
588 } else {
589 for k in 0..l {
590 self.g_bar[k] += c_i * q_i_full[k] as f64;
591 }
592 }
593 }
594
595 if uj != self.is_upper_bound(j) {
596 let q_j_full = self.q.get_q(j, l).to_vec();
597 if uj {
598 for k in 0..l {
599 self.g_bar[k] -= c_j * q_j_full[k] as f64;
600 }
601 } else {
602 for k in 0..l {
603 self.g_bar[k] += c_j * q_j_full[k] as f64;
604 }
605 }
606 }
607 }
608
609 fn do_shrinking(&mut self) {
612 match self.variant {
613 SolverVariant::Standard => self.do_shrinking_standard(),
614 SolverVariant::Nu => self.do_shrinking_nu(),
615 }
616 }
617
618 fn be_shrunk_standard(&self, i: usize, gmax1: f64, gmax2: f64) -> bool {
619 if self.is_upper_bound(i) {
620 if self.y[i] == 1 {
621 -self.g[i] > gmax1
622 } else {
623 -self.g[i] > gmax2
624 }
625 } else if self.is_lower_bound(i) {
626 if self.y[i] == 1 {
627 self.g[i] > gmax2
628 } else {
629 self.g[i] > gmax1
630 }
631 } else {
632 false
633 }
634 }
635
636 fn do_shrinking_standard(&mut self) {
637 let mut gmax1 = -INF;
638 let mut gmax2 = -INF;
639
640 for i in 0..self.active_size {
641 if self.y[i] == 1 {
642 if !self.is_upper_bound(i) && -self.g[i] >= gmax1 {
643 gmax1 = -self.g[i];
644 }
645 if !self.is_lower_bound(i) && self.g[i] >= gmax2 {
646 gmax2 = self.g[i];
647 }
648 } else {
649 if !self.is_upper_bound(i) && -self.g[i] >= gmax2 {
650 gmax2 = -self.g[i];
651 }
652 if !self.is_lower_bound(i) && self.g[i] >= gmax1 {
653 gmax1 = self.g[i];
654 }
655 }
656 }
657
658 if !self.unshrink && gmax1 + gmax2 <= self.eps * 10.0 {
659 self.unshrink = true;
660 self.reconstruct_gradient();
661 self.active_size = self.l;
662 }
663
664 let mut i = 0;
665 while i < self.active_size {
666 if self.be_shrunk_standard(i, gmax1, gmax2) {
667 self.active_size -= 1;
668 while self.active_size > i {
669 if !self.be_shrunk_standard(self.active_size, gmax1, gmax2) {
670 self.swap_index(i, self.active_size);
671 break;
672 }
673 self.active_size -= 1;
674 }
675 }
676 i += 1;
677 }
678 }
679
680 fn be_shrunk_nu(&self, i: usize, gmax1: f64, gmax2: f64, gmax3: f64, gmax4: f64) -> bool {
681 if self.is_upper_bound(i) {
682 if self.y[i] == 1 {
683 -self.g[i] > gmax1
684 } else {
685 -self.g[i] > gmax4
686 }
687 } else if self.is_lower_bound(i) {
688 if self.y[i] == 1 {
689 self.g[i] > gmax2
690 } else {
691 self.g[i] > gmax3
692 }
693 } else {
694 false
695 }
696 }
697
698 fn do_shrinking_nu(&mut self) {
699 let mut gmax1 = -INF;
700 let mut gmax2 = -INF;
701 let mut gmax3 = -INF;
702 let mut gmax4 = -INF;
703
704 for i in 0..self.active_size {
705 if !self.is_upper_bound(i) {
706 if self.y[i] == 1 {
707 if -self.g[i] > gmax1 {
708 gmax1 = -self.g[i];
709 }
710 } else if -self.g[i] > gmax4 {
711 gmax4 = -self.g[i];
712 }
713 }
714 if !self.is_lower_bound(i) {
715 if self.y[i] == 1 {
716 if self.g[i] > gmax2 {
717 gmax2 = self.g[i];
718 }
719 } else if self.g[i] > gmax3 {
720 gmax3 = self.g[i];
721 }
722 }
723 }
724
725 if !self.unshrink && f64::max(gmax1 + gmax2, gmax3 + gmax4) <= self.eps * 10.0 {
726 self.unshrink = true;
727 self.reconstruct_gradient();
728 self.active_size = self.l;
729 }
730
731 let mut i = 0;
732 while i < self.active_size {
733 if self.be_shrunk_nu(i, gmax1, gmax2, gmax3, gmax4) {
734 self.active_size -= 1;
735 while self.active_size > i {
736 if !self.be_shrunk_nu(self.active_size, gmax1, gmax2, gmax3, gmax4) {
737 self.swap_index(i, self.active_size);
738 break;
739 }
740 self.active_size -= 1;
741 }
742 }
743 i += 1;
744 }
745 }
746
747 fn calculate_rho(&self) -> (f64, f64) {
750 match self.variant {
751 SolverVariant::Standard => (self.calculate_rho_standard(), 0.0),
752 SolverVariant::Nu => self.calculate_rho_nu(),
753 }
754 }
755
756 fn calculate_rho_standard(&self) -> f64 {
757 let mut nr_free = 0;
758 let mut ub = INF;
759 let mut lb = -INF;
760 let mut sum_free = 0.0;
761
762 for i in 0..self.active_size {
763 let yg = self.y[i] as f64 * self.g[i];
764
765 if self.is_upper_bound(i) {
766 if self.y[i] == -1 {
767 ub = ub.min(yg);
768 } else {
769 lb = lb.max(yg);
770 }
771 } else if self.is_lower_bound(i) {
772 if self.y[i] == 1 {
773 ub = ub.min(yg);
774 } else {
775 lb = lb.max(yg);
776 }
777 } else {
778 nr_free += 1;
779 sum_free += yg;
780 }
781 }
782
783 if nr_free > 0 {
784 sum_free / nr_free as f64
785 } else {
786 (ub + lb) / 2.0
787 }
788 }
789
790 fn calculate_rho_nu(&self) -> (f64, f64) {
791 let mut nr_free1 = 0;
792 let mut nr_free2 = 0;
793 let mut ub1 = INF;
794 let mut ub2 = INF;
795 let mut lb1 = -INF;
796 let mut lb2 = -INF;
797 let mut sum_free1 = 0.0;
798 let mut sum_free2 = 0.0;
799
800 for i in 0..self.active_size {
801 if self.y[i] == 1 {
802 if self.is_upper_bound(i) {
803 lb1 = lb1.max(self.g[i]);
804 } else if self.is_lower_bound(i) {
805 ub1 = ub1.min(self.g[i]);
806 } else {
807 nr_free1 += 1;
808 sum_free1 += self.g[i];
809 }
810 } else if self.is_upper_bound(i) {
811 lb2 = lb2.max(self.g[i]);
812 } else if self.is_lower_bound(i) {
813 ub2 = ub2.min(self.g[i]);
814 } else {
815 nr_free2 += 1;
816 sum_free2 += self.g[i];
817 }
818 }
819
820 let r1 = if nr_free1 > 0 {
821 sum_free1 / nr_free1 as f64
822 } else {
823 (ub1 + lb1) / 2.0
824 };
825
826 let r2 = if nr_free2 > 0 {
827 sum_free2 / nr_free2 as f64
828 } else {
829 (ub2 + lb2) / 2.0
830 };
831
832 let rho = (r1 - r2) / 2.0;
833 let r = (r1 + r2) / 2.0;
834 (rho, r)
835 }
836}