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!(
214 "optimization finished, #iter = {}\n",
215 iter
216 ));
217
218 si
219 }
220
221 #[inline]
224 fn get_c(&self, i: usize) -> f64 {
225 if self.y[i] > 0 { self.cp } else { self.cn }
226 }
227
228 #[inline]
229 fn update_alpha_status(&mut self, i: usize) {
230 self.alpha_status[i] = if self.alpha[i] >= self.get_c(i) {
231 AlphaStatus::UpperBound
232 } else if self.alpha[i] <= 0.0 {
233 AlphaStatus::LowerBound
234 } else {
235 AlphaStatus::Free
236 };
237 }
238
239 #[inline]
240 fn is_upper_bound(&self, i: usize) -> bool {
241 self.alpha_status[i] == AlphaStatus::UpperBound
242 }
243
244 #[inline]
245 fn is_lower_bound(&self, i: usize) -> bool {
246 self.alpha_status[i] == AlphaStatus::LowerBound
247 }
248
249 #[inline]
250 fn is_free(&self, i: usize) -> bool {
251 self.alpha_status[i] == AlphaStatus::Free
252 }
253
254 fn swap_index(&mut self, i: usize, j: usize) {
255 self.q.swap_index(i, j);
256 self.y.swap(i, j);
257 self.g.swap(i, j);
258 self.alpha_status.swap(i, j);
259 self.alpha.swap(i, j);
260 self.p.swap(i, j);
261 self.active_set.swap(i, j);
262 self.g_bar.swap(i, j);
263 self.qd.swap(i, j);
264 }
265
266 fn reconstruct_gradient(&mut self) {
267 if self.active_size == self.l {
268 return;
269 }
270
271 for j in self.active_size..self.l {
272 self.g[j] = self.g_bar[j] + self.p[j];
273 }
274
275 let mut nr_free = 0;
276 for j in 0..self.active_size {
277 if self.is_free(j) {
278 nr_free += 1;
279 }
280 }
281
282 if 2 * nr_free < self.active_size {
283 crate::info("WARNING: using -h 0 may be faster\n");
284 }
285
286 let active_size = self.active_size;
287 let l = self.l;
288
289 if nr_free * l > 2 * active_size * (l - active_size) {
290 for i in active_size..l {
291 let q_i = self.q.get_q(i, active_size).to_vec();
292 for j in 0..active_size {
293 if self.is_free(j) {
294 self.g[i] += self.alpha[j] * q_i[j] as f64;
295 }
296 }
297 }
298 } else {
299 for i in 0..active_size {
300 if self.is_free(i) {
301 let q_i = self.q.get_q(i, l).to_vec();
302 let alpha_i = self.alpha[i];
303 for j in active_size..l {
304 self.g[j] += alpha_i * q_i[j] as f64;
305 }
306 }
307 }
308 }
309 }
310
311 fn select_working_set(&mut self) -> Option<(usize, usize)> {
315 match self.variant {
316 SolverVariant::Standard => self.select_working_set_standard(),
317 SolverVariant::Nu => self.select_working_set_nu(),
318 }
319 }
320
321 fn select_working_set_standard(&mut self) -> Option<(usize, usize)> {
322 let mut gmax = -INF;
323 let mut gmax2 = -INF;
324 let mut gmax_idx: Option<usize> = None;
325 let mut gmin_idx: Option<usize> = None;
326 let mut obj_diff_min = INF;
327
328 for t in 0..self.active_size {
330 if self.y[t] == 1 {
331 if !self.is_upper_bound(t) && -self.g[t] >= gmax {
332 gmax = -self.g[t];
333 gmax_idx = Some(t);
334 }
335 } else if !self.is_lower_bound(t) && self.g[t] >= gmax {
336 gmax = self.g[t];
337 gmax_idx = Some(t);
338 }
339 }
340
341 let i = gmax_idx?;
342 let q_i = self.q.get_q(i, self.active_size).to_vec();
343
344 for j in 0..self.active_size {
346 if self.y[j] == 1 {
347 if !self.is_lower_bound(j) {
348 let grad_diff = gmax + self.g[j];
349 if self.g[j] >= gmax2 {
350 gmax2 = self.g[j];
351 }
352 if grad_diff > 0.0 {
353 let quad_coef = self.qd[i] + self.qd[j]
354 - 2.0 * (self.y[i] as f64) * q_i[j] as f64;
355 let obj_diff = if quad_coef > 0.0 {
356 -(grad_diff * grad_diff) / quad_coef
357 } else {
358 -(grad_diff * grad_diff) / TAU
359 };
360 if obj_diff <= obj_diff_min {
361 gmin_idx = Some(j);
362 obj_diff_min = obj_diff;
363 }
364 }
365 }
366 } else if !self.is_upper_bound(j) {
367 let grad_diff = gmax - self.g[j];
368 if -self.g[j] >= gmax2 {
369 gmax2 = -self.g[j];
370 }
371 if grad_diff > 0.0 {
372 let quad_coef = self.qd[i] + self.qd[j]
373 + 2.0 * (self.y[i] as f64) * q_i[j] as f64;
374 let obj_diff = if quad_coef > 0.0 {
375 -(grad_diff * grad_diff) / quad_coef
376 } else {
377 -(grad_diff * grad_diff) / TAU
378 };
379 if obj_diff <= obj_diff_min {
380 gmin_idx = Some(j);
381 obj_diff_min = obj_diff;
382 }
383 }
384 }
385 }
386
387 if gmax + gmax2 < self.eps || gmin_idx.is_none() {
388 return None;
389 }
390
391 Some((i, gmin_idx.unwrap()))
392 }
393
394 fn select_working_set_nu(&mut self) -> Option<(usize, usize)> {
395 let mut gmaxp = -INF;
396 let mut gmaxp2 = -INF;
397 let mut gmaxp_idx: Option<usize> = None;
398 let mut gmaxn = -INF;
399 let mut gmaxn2 = -INF;
400 let mut gmaxn_idx: Option<usize> = None;
401 let mut gmin_idx: Option<usize> = None;
402 let mut obj_diff_min = INF;
403
404 for t in 0..self.active_size {
405 if self.y[t] == 1 {
406 if !self.is_upper_bound(t) && -self.g[t] >= gmaxp {
407 gmaxp = -self.g[t];
408 gmaxp_idx = Some(t);
409 }
410 } else if !self.is_lower_bound(t) && self.g[t] >= gmaxn {
411 gmaxn = self.g[t];
412 gmaxn_idx = Some(t);
413 }
414 }
415
416 let ip = gmaxp_idx;
417 let in_ = gmaxn_idx;
418
419 let q_ip = if let Some(ip) = ip {
420 Some(self.q.get_q(ip, self.active_size).to_vec())
421 } else {
422 None
423 };
424 let q_in = if let Some(in_) = in_ {
425 Some(self.q.get_q(in_, self.active_size).to_vec())
426 } else {
427 None
428 };
429
430 for j in 0..self.active_size {
431 if self.y[j] == 1 {
432 if !self.is_lower_bound(j) {
433 let grad_diff = gmaxp + self.g[j];
434 if self.g[j] >= gmaxp2 {
435 gmaxp2 = self.g[j];
436 }
437 if grad_diff > 0.0 {
438 if let (Some(ip), Some(ref q_ip)) = (ip, &q_ip) {
439 let quad_coef = self.qd[ip] + self.qd[j] - 2.0 * q_ip[j] as f64;
440 let obj_diff = if quad_coef > 0.0 {
441 -(grad_diff * grad_diff) / quad_coef
442 } else {
443 -(grad_diff * grad_diff) / TAU
444 };
445 if obj_diff <= obj_diff_min {
446 gmin_idx = Some(j);
447 obj_diff_min = obj_diff;
448 }
449 }
450 }
451 }
452 } else if !self.is_upper_bound(j) {
453 let grad_diff = gmaxn - self.g[j];
454 if -self.g[j] >= gmaxn2 {
455 gmaxn2 = -self.g[j];
456 }
457 if grad_diff > 0.0 {
458 if let (Some(in_), Some(ref q_in)) = (in_, &q_in) {
459 let quad_coef = self.qd[in_] + self.qd[j] - 2.0 * q_in[j] as f64;
460 let obj_diff = if quad_coef > 0.0 {
461 -(grad_diff * grad_diff) / quad_coef
462 } else {
463 -(grad_diff * grad_diff) / TAU
464 };
465 if obj_diff <= obj_diff_min {
466 gmin_idx = Some(j);
467 obj_diff_min = obj_diff;
468 }
469 }
470 }
471 }
472 }
473
474 if f64::max(gmaxp + gmaxp2, gmaxn + gmaxn2) < self.eps || gmin_idx.is_none() {
475 return None;
476 }
477
478 let out_j = gmin_idx.unwrap();
479 let out_i = if self.y[out_j] == 1 {
480 gmaxp_idx?
481 } else {
482 gmaxn_idx?
483 };
484
485 Some((out_i, out_j))
486 }
487
488 fn update_alpha_pair(&mut self, i: usize, j: usize) {
491 let active_size = self.active_size;
492 let q_i = self.q.get_q(i, active_size).to_vec();
493 let q_j = self.q.get_q(j, active_size).to_vec();
494
495 let c_i = self.get_c(i);
496 let c_j = self.get_c(j);
497
498 let old_alpha_i = self.alpha[i];
499 let old_alpha_j = self.alpha[j];
500
501 if self.y[i] != self.y[j] {
502 let mut quad_coef = self.qd[i] + self.qd[j] + 2.0 * q_i[j] as f64;
503 if quad_coef <= 0.0 {
504 quad_coef = TAU;
505 }
506 let delta = (-self.g[i] - self.g[j]) / quad_coef;
507 let diff = self.alpha[i] - self.alpha[j];
508 self.alpha[i] += delta;
509 self.alpha[j] += delta;
510
511 if diff > 0.0 {
512 if self.alpha[j] < 0.0 {
513 self.alpha[j] = 0.0;
514 self.alpha[i] = diff;
515 }
516 } else if self.alpha[i] < 0.0 {
517 self.alpha[i] = 0.0;
518 self.alpha[j] = -diff;
519 }
520 if diff > c_i - c_j {
521 if self.alpha[i] > c_i {
522 self.alpha[i] = c_i;
523 self.alpha[j] = c_i - diff;
524 }
525 } else if self.alpha[j] > c_j {
526 self.alpha[j] = c_j;
527 self.alpha[i] = c_j + diff;
528 }
529 } else {
530 let mut quad_coef = self.qd[i] + self.qd[j] - 2.0 * q_i[j] as f64;
531 if quad_coef <= 0.0 {
532 quad_coef = TAU;
533 }
534 let delta = (self.g[i] - self.g[j]) / quad_coef;
535 let sum = self.alpha[i] + self.alpha[j];
536 self.alpha[i] -= delta;
537 self.alpha[j] += delta;
538
539 if sum > c_i {
540 if self.alpha[i] > c_i {
541 self.alpha[i] = c_i;
542 self.alpha[j] = sum - c_i;
543 }
544 } else if self.alpha[j] < 0.0 {
545 self.alpha[j] = 0.0;
546 self.alpha[i] = sum;
547 }
548 if sum > c_j {
549 if self.alpha[j] > c_j {
550 self.alpha[j] = c_j;
551 self.alpha[i] = sum - c_j;
552 }
553 } else if self.alpha[i] < 0.0 {
554 self.alpha[i] = 0.0;
555 self.alpha[j] = sum;
556 }
557 }
558
559 let delta_alpha_i = self.alpha[i] - old_alpha_i;
561 let delta_alpha_j = self.alpha[j] - old_alpha_j;
562
563 for k in 0..active_size {
564 self.g[k] += q_i[k] as f64 * delta_alpha_i + q_j[k] as f64 * delta_alpha_j;
565 }
566
567 let ui = self.is_upper_bound(i);
569 let uj = self.is_upper_bound(j);
570 self.update_alpha_status(i);
571 self.update_alpha_status(j);
572
573 let l = self.l;
574
575 if ui != self.is_upper_bound(i) {
576 let q_i_full = self.q.get_q(i, l).to_vec();
577 if ui {
578 for k in 0..l {
579 self.g_bar[k] -= c_i * q_i_full[k] as f64;
580 }
581 } else {
582 for k in 0..l {
583 self.g_bar[k] += c_i * q_i_full[k] as f64;
584 }
585 }
586 }
587
588 if uj != self.is_upper_bound(j) {
589 let q_j_full = self.q.get_q(j, l).to_vec();
590 if uj {
591 for k in 0..l {
592 self.g_bar[k] -= c_j * q_j_full[k] as f64;
593 }
594 } else {
595 for k in 0..l {
596 self.g_bar[k] += c_j * q_j_full[k] as f64;
597 }
598 }
599 }
600 }
601
602 fn do_shrinking(&mut self) {
605 match self.variant {
606 SolverVariant::Standard => self.do_shrinking_standard(),
607 SolverVariant::Nu => self.do_shrinking_nu(),
608 }
609 }
610
611 fn be_shrunk_standard(&self, i: usize, gmax1: f64, gmax2: f64) -> bool {
612 if self.is_upper_bound(i) {
613 if self.y[i] == 1 {
614 -self.g[i] > gmax1
615 } else {
616 -self.g[i] > gmax2
617 }
618 } else if self.is_lower_bound(i) {
619 if self.y[i] == 1 {
620 self.g[i] > gmax2
621 } else {
622 self.g[i] > gmax1
623 }
624 } else {
625 false
626 }
627 }
628
629 fn do_shrinking_standard(&mut self) {
630 let mut gmax1 = -INF;
631 let mut gmax2 = -INF;
632
633 for i in 0..self.active_size {
634 if self.y[i] == 1 {
635 if !self.is_upper_bound(i) && -self.g[i] >= gmax1 {
636 gmax1 = -self.g[i];
637 }
638 if !self.is_lower_bound(i) && self.g[i] >= gmax2 {
639 gmax2 = self.g[i];
640 }
641 } else {
642 if !self.is_upper_bound(i) && -self.g[i] >= gmax2 {
643 gmax2 = -self.g[i];
644 }
645 if !self.is_lower_bound(i) && self.g[i] >= gmax1 {
646 gmax1 = self.g[i];
647 }
648 }
649 }
650
651 if !self.unshrink && gmax1 + gmax2 <= self.eps * 10.0 {
652 self.unshrink = true;
653 self.reconstruct_gradient();
654 self.active_size = self.l;
655 }
656
657 let mut i = 0;
658 while i < self.active_size {
659 if self.be_shrunk_standard(i, gmax1, gmax2) {
660 self.active_size -= 1;
661 while self.active_size > i {
662 if !self.be_shrunk_standard(self.active_size, gmax1, gmax2) {
663 self.swap_index(i, self.active_size);
664 break;
665 }
666 self.active_size -= 1;
667 }
668 }
669 i += 1;
670 }
671 }
672
673 fn be_shrunk_nu(&self, i: usize, gmax1: f64, gmax2: f64, gmax3: f64, gmax4: f64) -> bool {
674 if self.is_upper_bound(i) {
675 if self.y[i] == 1 {
676 -self.g[i] > gmax1
677 } else {
678 -self.g[i] > gmax4
679 }
680 } else if self.is_lower_bound(i) {
681 if self.y[i] == 1 {
682 self.g[i] > gmax2
683 } else {
684 self.g[i] > gmax3
685 }
686 } else {
687 false
688 }
689 }
690
691 fn do_shrinking_nu(&mut self) {
692 let mut gmax1 = -INF;
693 let mut gmax2 = -INF;
694 let mut gmax3 = -INF;
695 let mut gmax4 = -INF;
696
697 for i in 0..self.active_size {
698 if !self.is_upper_bound(i) {
699 if self.y[i] == 1 {
700 if -self.g[i] > gmax1 { gmax1 = -self.g[i]; }
701 } else if -self.g[i] > gmax4 { gmax4 = -self.g[i]; }
702 }
703 if !self.is_lower_bound(i) {
704 if self.y[i] == 1 {
705 if self.g[i] > gmax2 { gmax2 = self.g[i]; }
706 } else if self.g[i] > gmax3 { gmax3 = self.g[i]; }
707 }
708 }
709
710 if !self.unshrink && f64::max(gmax1 + gmax2, gmax3 + gmax4) <= self.eps * 10.0 {
711 self.unshrink = true;
712 self.reconstruct_gradient();
713 self.active_size = self.l;
714 }
715
716 let mut i = 0;
717 while i < self.active_size {
718 if self.be_shrunk_nu(i, gmax1, gmax2, gmax3, gmax4) {
719 self.active_size -= 1;
720 while self.active_size > i {
721 if !self.be_shrunk_nu(self.active_size, gmax1, gmax2, gmax3, gmax4) {
722 self.swap_index(i, self.active_size);
723 break;
724 }
725 self.active_size -= 1;
726 }
727 }
728 i += 1;
729 }
730 }
731
732 fn calculate_rho(&self) -> (f64, f64) {
735 match self.variant {
736 SolverVariant::Standard => (self.calculate_rho_standard(), 0.0),
737 SolverVariant::Nu => self.calculate_rho_nu(),
738 }
739 }
740
741 fn calculate_rho_standard(&self) -> f64 {
742 let mut nr_free = 0;
743 let mut ub = INF;
744 let mut lb = -INF;
745 let mut sum_free = 0.0;
746
747 for i in 0..self.active_size {
748 let yg = self.y[i] as f64 * self.g[i];
749
750 if self.is_upper_bound(i) {
751 if self.y[i] == -1 {
752 ub = ub.min(yg);
753 } else {
754 lb = lb.max(yg);
755 }
756 } else if self.is_lower_bound(i) {
757 if self.y[i] == 1 {
758 ub = ub.min(yg);
759 } else {
760 lb = lb.max(yg);
761 }
762 } else {
763 nr_free += 1;
764 sum_free += yg;
765 }
766 }
767
768 if nr_free > 0 {
769 sum_free / nr_free as f64
770 } else {
771 (ub + lb) / 2.0
772 }
773 }
774
775 fn calculate_rho_nu(&self) -> (f64, f64) {
776 let mut nr_free1 = 0;
777 let mut nr_free2 = 0;
778 let mut ub1 = INF;
779 let mut ub2 = INF;
780 let mut lb1 = -INF;
781 let mut lb2 = -INF;
782 let mut sum_free1 = 0.0;
783 let mut sum_free2 = 0.0;
784
785 for i in 0..self.active_size {
786 if self.y[i] == 1 {
787 if self.is_upper_bound(i) {
788 lb1 = lb1.max(self.g[i]);
789 } else if self.is_lower_bound(i) {
790 ub1 = ub1.min(self.g[i]);
791 } else {
792 nr_free1 += 1;
793 sum_free1 += self.g[i];
794 }
795 } else if self.is_upper_bound(i) {
796 lb2 = lb2.max(self.g[i]);
797 } else if self.is_lower_bound(i) {
798 ub2 = ub2.min(self.g[i]);
799 } else {
800 nr_free2 += 1;
801 sum_free2 += self.g[i];
802 }
803 }
804
805 let r1 = if nr_free1 > 0 {
806 sum_free1 / nr_free1 as f64
807 } else {
808 (ub1 + lb1) / 2.0
809 };
810
811 let r2 = if nr_free2 > 0 {
812 sum_free2 / nr_free2 as f64
813 } else {
814 (ub2 + lb2) / 2.0
815 };
816
817 let rho = (r1 - r2) / 2.0;
818 let r = (r1 + r2) / 2.0;
819 (rho, r)
820 }
821}