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