1use crate::algebra::*;
4use crate::expr::Expr;
5use crate::flag::Flag;
6use crate::operator::*;
7use crate::sdpa::*;
8use crate::tools::csdp;
9use crate::tools::csdp_minimize_certificate;
10use arrayvec::ArrayVec;
11use ndarray_linalg::{Cholesky, Eigh, UPLO};
12use num::*;
13use sprs::{CsMat, TriMat};
14use std::ops::Index;
15
16use ndarray::{Array1, Array2, ScalarOperand};
17use std::fmt;
18use std::fmt::Display;
19use std::fs::File;
20use std::io;
21use std::io::{BufRead, BufReader, BufWriter, Write};
22use std::ops::{AddAssign, DivAssign, Neg};
23use std::path::PathBuf;
24
25use log::*;
26
27#[derive(Debug, Clone)]
40pub struct Problem<N, F: Flag> {
41 pub ineqs: Vec<Ineq<N, F>>,
43 pub cs: Vec<MulAndUnlabel<F>>,
45 pub obj: QFlag<N, F>,
47}
48
49fn write_coeff<N>(
51 file: &mut BufWriter<File>,
52 mat_num: usize,
53 block_num: usize,
54 i: usize,
55 j: usize,
56 value: N,
57) -> io::Result<()>
58where
59 N: Display + Zero + PartialEq,
60{
61 if value != N::zero() {
62 writeln!(
63 file,
64 "{} {} {} {} {}",
65 mat_num,
66 block_num + 1,
67 i + 1,
68 j + 1,
69 value
70 )?;
71 }
72 Ok(())
73}
74
75fn write_ineq_coeff<N>(
78 file: &mut BufWriter<File>,
79 mat_num: usize,
80 block_num: usize,
81 i: usize,
82 value: N,
83 equality: bool,
84) -> io::Result<()>
85where
86 N: Display + Zero + PartialEq + Neg<Output = N> + Copy,
87{
88 if equality {
89 write_coeff(file, mat_num, block_num, 2 * i, 2 * i, value)?;
90 write_coeff(file, mat_num, block_num, 2 * i + 1, 2 * i + 1, -value)?;
91 } else {
92 write_coeff(file, mat_num, block_num, i, i, value)?;
93 }
94 Ok(())
95}
96
97impl<N, F: Flag> Problem<N, F> {
98 pub fn minimize(obj: QFlag<N, F>) -> Self
100 where
101 N: Display + Num + FromPrimitive + Clone + Neg<Output = N>,
102 {
103 Self {
104 ineqs: vec![
105 flags_are_nonnegative(obj.basis),
106 total_sum_is_one(obj.basis),
107 ],
108 cs: obj.basis.all_cs(),
109 obj,
110 }
111 }
112 pub fn check(&self) {
114 let b = self.obj.basis;
115 for ineq in &self.ineqs {
116 assert_eq!(ineq.meta.basis, b);
117 }
118 for cs in &self.cs {
119 assert_eq!(cs.output_basis(), b);
120 }
121 }
122 pub(crate) fn view<'a>(&'a self, selector: &'a Selector) -> ProblemView<'a, N, F> {
123 self.check();
124 let ineqs = Select {
125 selected: &self.ineqs,
126 selector: &selector.ineqs,
127 };
128 let cs = Select {
129 selected: &self.cs,
130 selector: &selector.cs,
131 };
132 ProblemView {
133 obj: &self.obj,
134 ineqs,
135 cs,
136 cs_subspace: selector.cs_subspace_constraints().collect(),
137 }
138 }
139}
140
141fn format_count(count: usize, max: usize) -> String {
142 if count == max {
143 format!("{count}")
144 } else {
145 format!("{count}/{max}")
146 }
147}
148
149impl<'a, N, F> ProblemView<'a, N, F>
150where
151 N: Display + Neg<Output = N> + Zero + Copy + PartialEq,
152 F: Flag,
153{
154 fn write_header<W: Write>(&self, file: &mut W) -> io::Result<()> {
156 writeln!(file, "* Semi-Definite Problem generated by Rust")?;
157 writeln!(file, "*")?;
158 writeln!(file, "* Flags: {}", F::NAME)?;
159 writeln!(
160 file,
161 "* Basis: {:} ({} flags)",
162 self.obj.basis,
163 self.obj.basis.get().len()
164 )?;
165 writeln!(
166 file,
167 "* {} groups of linear constraints ({})",
168 self.ineqs.len(),
169 format_count(
170 self.ineqs.iter().map(|i| { i.len() }).sum::<usize>(),
171 self.ineqs
172 .iter()
173 .map(|ineq| { ineq.selected.data.len() })
174 .sum::<usize>()
175 )
176 )?;
177 writeln!(
178 file,
179 "* {} Cauchy-Schwarz constraints",
180 format_count(self.cs.len(), self.cs.selected.len())
181 )?;
182 writeln!(
183 file,
184 "* {} additional constraints on Cauchy-Schwarz matrices",
185 self.cs_subspace.len(),
186 )?;
187 writeln!(file, "*")?;
188 write!(file, "* Minimizing: {}", self.obj.expr)?;
189 match self.obj.scale {
190 1 => writeln!(file)?,
191 s => writeln!(file, " (scale {s})")?,
192 }
193 writeln!(file, "* Under the constraints:")?;
194 for ineqs in self.ineqs.iter() {
195 writeln!(
196 file,
197 "* # {} ({})",
198 ineqs.meta(),
199 format_count(ineqs.len(), ineqs.selected.data.len())
200 )?;
201 }
202 for cs in self.cs.iter() {
203 writeln!(
204 file,
205 "* # Cauchy-Schwarz: {}x{}; {:?}",
206 cs.1.split.left_basis().size,
207 cs.1.split.right_basis().size,
208 cs.1.split.right_basis().t,
209 )?;
210 }
211 writeln!(file, "*")?;
212 Ok(())
213 }
214 pub fn write_sdpa(&self, filename: &str) -> io::Result<()> {
216 let mut filename = PathBuf::from(filename);
217 let _ = filename.set_extension("sdpa");
218 info!("Writing problem in {}", filename.display());
219 let mut file = BufWriter::new(File::create(&filename)?);
220 self.write_header(&mut file)?;
221 debug!("Generating Cauchy-Schwarz inequalities");
222 let cs_mat: Vec<_> = self.cs.get();
223 writeln!(file, "{}", self.obj.data.len() + self.cs_subspace.len())?;
224 writeln!(file, "{}", self.ineqs.len() + self.cs.len())?;
226 for ineq in self.ineqs.iter() {
228 assert!(ineq.len() > 0);
229 write!(file, "-{} ", ineq.len_spliting_equalities())?;
230 }
231 for split in &cs_mat {
232 write!(file, "{} ", split[0].rows())?;
233 }
234 writeln!(file)?;
235 for v in &self.obj.data {
238 write!(file, "{v} ")?;
239 }
240 for _ in &self.cs_subspace {
241 write!(file, "0 ")?;
242 }
243 write!(file, "\n\n")?;
244 for (block_num, ineq) in self.ineqs.iter().enumerate() {
247 for (i, ineq_data) in ineq.iter().enumerate() {
248 write_ineq_coeff(
249 &mut file,
250 0,
251 block_num,
252 i,
253 ineq_data.bound,
254 ineq.meta().equality,
255 )?;
256 }
257 }
258 writeln!(file)?;
259 for (block_num, ineq) in self.ineqs.iter().enumerate() {
262 for (i, ineq_data) in ineq.iter().enumerate() {
263 for (mat_num, &v) in ineq_data.flag.iter() {
264 write_ineq_coeff(
265 &mut file,
266 mat_num + 1,
267 block_num,
268 i,
269 v,
270 ineq.meta().equality,
271 )?;
272 }
273 }
274 }
275 writeln!(file)?;
276 let block_offset = self.ineqs.len();
278 for (block_num, line) in cs_mat.iter().enumerate() {
279 for (mat_num, matrix) in line.iter().enumerate() {
280 for (&v, (i, j)) in matrix {
281 if i <= j {
282 write_coeff(&mut file, mat_num + 1, block_num + block_offset, i, j, v)?;
283 }
284 }
285 }
286 }
287 let mat_offset = self.obj.data.len();
289 for (i_mat, (block_num, matrix)) in self.cs_subspace.iter().enumerate() {
290 for (&v, (i, j)) in *matrix {
291 if i <= j {
292 let mat_num = mat_offset + i_mat + 1;
293 write_coeff(&mut file, mat_num, block_num + block_offset, i, j, v)?;
294 }
295 }
296 }
297 Ok(())
298 }
299}
300
301impl<N, F> Problem<N, F>
302where
303 N: Display + Zero + Copy + PartialEq + Neg<Output = N>,
304 F: Flag,
305{
306 pub fn write_sdpa(&self, filename: &str) -> io::Result<()> {
308 self.view(&Selector::new(self)).write_sdpa(filename)
309 }
310 pub fn no_scale(mut self) -> Self
313 where
314 N: DivAssign + ScalarOperand + FromPrimitive,
315 {
316 self.obj = self.obj.no_scale();
317 self
318 }
319 pub fn solve_csdp(&self, filename: &str) -> Result<f64, Error> {
321 self.write_sdpa(filename)?;
322 self.run_csdp(filename, None, false)
323 }
324 pub fn run_csdp(
325 &self,
326 name: &str,
327 initial_solution: Option<&str>,
328 minimize_certificate: bool,
329 ) -> Result<f64, Error> {
330 let filename = format!("{name}.sdpa");
331 match if minimize_certificate {
332 csdp_minimize_certificate(&filename, initial_solution)
333 } else {
334 csdp(&filename, initial_solution)
335 } {
336 Ok(v) => Ok(v / self.obj.scale as f64),
337 e => e,
338 }
339 }
340}
341
342#[derive(Debug, Clone, PartialEq)]
346pub struct Selector {
347 pub ineqs: Vec<Vec<usize>>,
348 cs: Vec<VecCsMode>,
349 cs_subspace: Vec<Subspace>,
350}
351
352#[derive(Debug, Clone, PartialEq)]
353struct Subspace {
354 basis_len: usize,
355 classes: usize,
356 orth: Vec<(CSMode, CsMat<f64>)>,
357}
358
359impl Subspace {
360 fn new<F: Flag>(cs: &MulAndUnlabel<F>) -> Self {
361 Self {
362 basis_len: Unlabel::total(cs.split.left_basis()).get().0.len(),
363 classes: cs.invariant_classes().get().into_iter().max().unwrap() + 1,
364 orth: Vec::new(),
365 }
366 }
367 fn dim(&self, mode: CSMode) -> usize {
368 match mode {
369 Simple => self.basis_len,
370 Invariant => self.classes,
371 AntiInvariant => self.basis_len - self.classes,
372 }
373 }
374 fn restrict(&mut self, mode: CSMode, mat: CsMat<f64>) {
375 self.orth.push((mode, mat))
376 }
377 fn orthogonal_matrices(&self, mode: CSMode) -> impl Iterator<Item = &CsMat<f64>> {
378 self.orth
379 .iter()
380 .filter(move |(mode2, _)| *mode2 == mode)
381 .map(|(_, mat)| mat)
382 }
383}
384
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
387pub enum CSMode {
388 Simple,
389 Invariant,
390 AntiInvariant,
391}
392
393use CSMode::*;
394
395type VecCsMode = ArrayVec<CSMode, 2>;
396
397#[derive(Debug, Clone)]
399pub struct CauchySchwarzMatrix<F: Flag>(pub CSMode, pub MulAndUnlabel<F>);
400
401impl<F: Flag> Display for CauchySchwarzMatrix<F> {
402 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
403 match self.0 {
404 Simple => write!(f, "{}", self.1),
405 Invariant => write!(f, "{} invariant", self.1),
406 AntiInvariant => write!(f, "{} anti-invariant", self.1),
407 }
408 }
409}
410
411impl<F: Flag> CauchySchwarzMatrix<F> {
412 pub fn get(&self) -> Vec<CsMat<i64>> {
413 match self.0 {
414 Simple => self.1.get(),
415 Invariant => self.1.reduced().get().0,
416 AntiInvariant => self.1.reduced().get().1,
417 }
418 }
419}
420
421#[derive(Debug, Clone)]
422pub(crate) struct Select<'a, A, B> {
423 selected: &'a A,
424 selector: &'a B,
425}
426
427#[derive(Debug, Clone)]
428pub(crate) struct SelectIter<'a, A, B> {
429 content: &'a Select<'a, A, B>,
430 iter: std::ops::Range<usize>,
431}
432
433#[derive(Debug, Clone)]
435pub struct ProblemView<'a, N, F: Flag> {
436 pub obj: &'a QFlag<N, F>,
437 pub(crate) ineqs: IneqsSelect<'a, N, F>,
438 pub(crate) cs: CsSelect<'a, F>,
439 pub(crate) cs_subspace: Vec<(usize, &'a CsMat<f64>)>,
440}
441
442type CsSelect<'a, F> = Select<'a, Vec<MulAndUnlabel<F>>, Vec<VecCsMode>>;
444type IneqsSelect<'a, N, F> = Select<'a, Vec<Ineq<N, F>>, Vec<Vec<usize>>>;
445type IneqSelect<'a, N, F> = Select<'a, Ineq<N, F>, Vec<usize>>;
446
447#[derive(Debug, Clone)]
448pub struct CsIter<'a, F: Flag> {
449 content: CsSelect<'a, F>,
450 iter: std::ops::Range<usize>,
451 next: Option<CauchySchwarzMatrix<F>>,
452}
453
454type IneqsIter<'a, N, F> = SelectIter<'a, Vec<Ineq<N, F>>, Vec<Vec<usize>>>;
455type IneqIter<'a, N, F> = SelectIter<'a, Ineq<N, F>, Vec<usize>>;
456
457impl<'a, F: Flag> CsSelect<'a, F> {
458 pub fn iter(&self) -> CsIter<'a, F> {
459 CsIter {
460 content: self.clone(),
461 iter: 0..self.selector.len(),
462 next: None,
463 }
464 }
465 pub fn len(&self) -> usize {
466 self.iter().count()
467 }
468 pub fn get(&self) -> Vec<Vec<CsMat<i64>>>
469 where
470 F: Flag,
471 {
472 self.iter().map(|mat| mat.get()).collect()
473 }
474}
475
476impl<'a, N, F: Flag> Index<usize> for IneqSelect<'a, N, F> {
477 type Output = IneqData<N>;
478
479 fn index(&self, i: usize) -> &Self::Output {
480 &self.selected.data[self.selector[i]]
481 }
482}
483
484impl<'a, N, F: Flag> IneqsSelect<'a, N, F> {
485 pub fn iter(&'a self) -> IneqsIter<'a, N, F> {
486 SelectIter {
487 content: self,
488 iter: 0..self.selector.len(),
489 }
490 }
491 pub fn len(&self) -> usize {
493 self.selector
494 .iter()
495 .filter(|select| !select.is_empty())
496 .count()
497 }
498 pub fn get(&self, i: usize) -> Option<IneqSelect<'a, N, F>> {
499 if self.selector[i].is_empty() {
500 None
501 } else {
502 Some(Select {
503 selector: &self.selector[i],
504 selected: &self.selected[i],
505 })
506 }
507 }
508}
509
510impl<'a, N, F: Flag> IneqSelect<'a, N, F> {
511 pub fn iter(&'a self) -> IneqIter<'a, N, F> {
512 SelectIter {
513 content: self,
514 iter: 0..self.selector.len(),
515 }
516 }
517 pub fn len(&self) -> usize {
519 self.selector.len()
520 }
521 pub fn len_spliting_equalities(&self) -> usize {
523 let len = self.len();
524 if self.meta().equality {
525 len * 2
526 } else {
527 len
528 }
529 }
530 pub fn meta(&self) -> &IneqMeta<N, F> {
531 &self.selected.meta
532 }
533}
534
535impl<'a, F: Flag> Iterator for CsIter<'a, F> {
537 type Item = CauchySchwarzMatrix<F>;
538
539 fn next(&mut self) -> Option<Self::Item> {
540 if let Some(x) = self.next.take() {
541 Some(x)
542 } else {
543 match self.iter.next() {
545 None => None,
546 Some(i) => {
547 let cs = self.content.selected[i];
548 let mat = |m| CauchySchwarzMatrix(m, cs);
549 let select_i = &self.content.selector[i];
550 match select_i.len() {
551 0 => self.next(),
552 1 => Some(mat(select_i[0])),
553 2 => {
554 self.next = Some(mat(select_i[0]));
555 Some(mat(select_i[1]))
556 }
557 _ => unimplemented!(),
558 }
559 }
560 }
561 }
562 }
563}
564
565impl<'a, N, F: Flag> Iterator for IneqIter<'a, N, F> {
566 type Item = &'a IneqData<N>;
567
568 fn next(&mut self) -> Option<Self::Item> {
569 self.iter.next().map(|i| &self.content[i])
570 }
571}
572
573impl<'a, N, F: Flag> Iterator for IneqsIter<'a, N, F> {
574 type Item = IneqSelect<'a, N, F>;
575
576 fn next(&mut self) -> Option<Self::Item> {
577 match self.iter.next() {
578 Some(i) => match self.content.get(i) {
579 None => self.next(),
580 some => some,
581 },
582 None => None,
583 }
584 }
585}
586
587type Id = (usize, CSMode);
588
589impl Selector {
590 pub fn new<N, F: Flag>(prob: &Problem<N, F>) -> Self {
591 let mut simple = ArrayVec::new();
592 simple.push(Simple); (Self {
595 ineqs: prob
596 .ineqs
597 .iter()
598 .map(|ineq| (0..ineq.data.len()).collect())
599 .collect(),
600 cs: vec![simple; prob.cs.len()],
601 cs_subspace: prob.cs.iter().map(Subspace::new).collect(), })
603 .variant_reduced(prob) }
605 pub fn variant_reduced<N, F>(mut self, problem: &Problem<N, F>) -> Self
606 where
607 F: Flag,
608 {
609 for (mode, cs) in self.cs.iter_mut().zip(&problem.cs) {
610 if mode.as_slice().contains(&Simple) {
611 mode.clear();
612 mode.push(Invariant);
613 let class = cs.invariant_classes().get();
614 let n = class.len();
615 if class[n - 1] < n - 1 {
616 mode.push(AntiInvariant);
617 }
618 }
619 }
620 self
621 }
622 pub fn cs_subspace_constraints(&self) -> impl Iterator<Item = (usize, &CsMat<f64>)> {
623 self.cs
624 .iter()
625 .zip(self.cs_subspace.iter())
626 .flat_map(|(cs_modes, subspace)| {
627 cs_modes
628 .iter()
629 .map(move |&mode| subspace.orthogonal_matrices(mode))
630 })
631 .enumerate()
632 .flat_map(|(i, iter_on_cs)| iter_on_cs.map(move |mat| (i, mat)))
633 }
634 pub fn weight(&self) -> (usize, usize) {
635 let count_ineq = self.ineqs.iter().map(|l| l.len()).sum();
636 (count_ineq, self.cs.len())
637 }
638 pub fn refine_with_certificate(&self, cert: &Certificate<f64>, protected: &[bool]) -> Self {
639 assert_eq!(self.ineqs.len(), protected.len());
640 let mut res = self.clone();
641 for (i, protect) in protected.iter().enumerate() {
642 if !protect {
643 for j in (0..self.ineqs[i].len()).rev() {
644 if match cert.x[i].get(j, j) {
645 None => true,
646 Some(v) => v < &1e-12f64,
647 } {
648 let _ = res.ineqs[i].remove(j);
649 }
650 }
651 }
652 }
653 res
654 }
655 pub fn cs_dim(&self, (i, mode): Id) -> usize {
656 self.cs_subspace[i].dim(mode)
657 }
658 pub fn remove_cs(&self, (i, mode): Id) -> Result<Self, String> {
659 match self.cs[i].iter().position(|&m| m == mode) {
660 None => Err(format!("No mode {mode:?} for index {i} to remove")),
661 Some(j) => {
662 let mut res = self.clone();
663 let _ = res.cs[i].swap_remove(j);
664 Ok(res)
665 }
666 }
667 }
668 pub fn cs_vec(&self) -> Vec<Id> {
669 let mut res = Vec::new();
670 for (i, vec) in self.cs.iter().enumerate() {
671 for &mode in vec.as_slice() {
672 res.push((i, mode))
673 }
674 }
675 res
676 }
677 pub fn restrict_cs(&self, (i, mode): Id, mat: CsMat<f64>) -> Result<Self, String> {
678 if self.cs[i].iter().any(|&m| m == mode) {
679 let mut res = self.clone();
681 res.cs_subspace[i].restrict(mode, mat);
682 Ok(res)
683 } else {
684 Err(format!("No mode {mode:?} for index {i} found"))
685 }
686 }
687}
688
689#[derive(Debug, Clone)]
691pub struct Certificate<N> {
692 pub y: Vec<N>,
693 pub z: Vec<CsMat<N>>, pub x: Vec<CsMat<N>>, }
696
697impl Certificate<f64> {
698 pub fn from_file_select<N, F: Flag>(
699 pb: &ProblemView<'_, N, F>,
700 name: &str,
701 ) -> io::Result<Self> {
702 let file = File::open(name)?;
703 let mut buf = BufReader::new(file).lines();
704 let y = buf
706 .next()
707 .unwrap()?
708 .split_whitespace()
709 .map(|x| x.parse::<f64>().unwrap())
710 .collect();
711 let mut tri_mat = [Vec::new(), Vec::new()];
713 for ineq in pb.ineqs.iter() {
714 let len = ineq.len_spliting_equalities(); assert!(len > 0);
716 for m in &mut tri_mat {
717 m.push(TriMat::new((len, len)))
718 }
719 }
720 for cs in &pb.cs.get() {
721 let n = cs[0].rows();
723 for m in &mut tri_mat {
724 m.push(TriMat::new((n, n)))
725 }
726 }
727 for line in buf {
728 let l = line.unwrap().parse::<SdpaCoeff>().unwrap();
729 tri_mat[l.mat - 1][l.block - 1].add_triplet(l.i - 1, l.j - 1, l.val);
730 if l.i != l.j {
732 tri_mat[l.mat - 1][l.block - 1].add_triplet(l.j - 1, l.i - 1, l.val);
733 }
734 }
735 for (i, ineq) in pb.ineqs.iter().enumerate() {
737 if ineq.meta().equality {
738 for blocks in &mut tri_mat {
739 let old_mat = &mut blocks[i];
740 let len = ineq.len();
741 let mut new_mat = TriMat::with_capacity((len, len), old_mat.nnz());
742 for (&val, (i, j)) in old_mat.triplet_iter() {
743 assert_eq!(i, j);
744 let new_val = if i % 2 == 0 { val } else { -val };
745 new_mat.add_triplet(i / 2, i / 2, new_val)
746 }
747 *old_mat = new_mat
748 }
749 }
750 }
751 let mut sprs_mat = tri_mat
753 .iter()
754 .map(|mat| mat.iter().map(|block| block.to_csc()).collect());
755 Ok(Self {
756 y,
757 z: sprs_mat.next().unwrap(), x: sprs_mat.next().unwrap(), })
760 }
761 pub fn to_file(&self, name: &str) -> io::Result<()> {
762 let mut w = BufWriter::new(File::create(name)?);
763 for v in &self.y {
765 write!(w, "{v} ")?;
766 }
767 writeln!(w)?;
768 for (num_mat, matrix) in [&self.z, &self.x].iter().enumerate() {
769 for (block, mat) in matrix.iter().enumerate() {
770 for (v, (i, j)) in mat {
771 if i <= j {
772 writeln!(w, "{} {} {} {} {}", num_mat + 1, block + 1, i + 1, j + 1, v)?;
773 }
774 }
775 }
776 }
777 Ok(())
778 }
779 pub fn value_primal<F, N>(&self, pb: &Problem<N, F>) -> f64
780 where
781 N: Clone + ToPrimitive,
782 F: Flag,
783 {
784 let mut res = 0.;
785 assert_eq!(pb.obj.data.len(), self.y.len());
786 for (ai, yi) in pb.obj.data.iter().zip(self.y.iter()) {
787 let ai: f64 = NumCast::from(ai.clone()).unwrap();
788 res += yi * ai;
789 }
790 res
791 }
792 pub fn value_dual<F, N>(&self, pb: &Problem<N, F>) -> f64
794 where
795 N: Clone + ToPrimitive,
796 F: Flag,
797 {
798 let mut res = 0.;
799 for (block, ineqs) in pb.ineqs.iter().enumerate() {
801 for (v, (i, j)) in &self.x[block] {
802 assert_eq!(i, j);
803 let bound: f64 = NumCast::from(ineqs.data[i].bound.clone()).unwrap();
804 res += v * bound;
805 }
806 }
807 res
808 }
809 pub fn values<F, N>(&self, pb: &Problem<N, F>) -> (f64, f64)
810 where
811 N: Clone + ToPrimitive,
812 F: Flag,
813 {
814 (self.value_primal(pb), self.value_dual(pb))
815 }
816 pub fn to_vec<F>(&self, b: Basis<F>, threshold: f64) -> QFlag<f64, F>
817 where
818 F: Flag,
819 {
820 b.qflag_from_vec(
821 self.y
822 .iter()
823 .map(|x| if x.abs() < threshold { 0. } else { *x })
824 .collect(),
825 )
826 }
827 pub fn with_threshold(mut self, threshold: f64) -> Self {
828 for x in &mut self.y {
829 if x.abs() < threshold {
830 *x = 0.
831 }
832 }
833 self
834 }
835 pub fn diag_coeffs(&self, block: usize, n: usize) -> Vec<f64> {
836 assert_eq!(self.x[block].cols(), n);
837 assert_eq!(self.x[block].rows(), n);
838 let mut res = vec![0.; n];
839 for (&v, (i, j)) in &self.x[block] {
840 assert_eq!(i, j);
841 res[i] += v;
842 }
843 res
844 }
845}
846
847pub(crate) fn condense<N, F>(ineq: IneqSelect<'_, N, F>, coeff: &[N]) -> (QFlag<N, F>, N)
848where
849 N: Num + Clone + ScalarOperand + AddAssign,
850 F: Flag,
851{
852 assert_eq!(ineq.len(), coeff.len());
853 assert!(!coeff.is_empty());
854 let mut bound = N::zero();
855 let mut res: Array1<N> = Array1::zeros(ineq.selected.data[0].flag.dim());
856 for (c, ineq_data) in coeff.iter().zip(ineq.iter()) {
857 if c != &N::zero() {
858 for (i, val) in ineq_data.flag.iter() {
859 res[i] += val.clone() * c.clone();
860 }
861 bound += ineq_data.bound.clone() * c.clone()
862 }
863 }
864 (
865 QFlag {
866 basis: ineq.meta().basis,
867 data: res,
868 scale: 1,
869 expr: ineq.meta().flag_expr.clone(),
870 },
871 bound,
872 )
873}
874
875fn hadamard<N>(dense: &Array2<N>, sprs: &CsMat<i64>) -> N
876where
877 N: Num + Clone + FromPrimitive,
878{
879 let mut res = N::zero();
880 for (&v, (i, j)) in sprs {
881 res = res + N::from_i64(v).unwrap() * dense[(i, j)].clone()
882 }
883 res
884}
885
886pub fn condense_cs<N, F>(cs: &CauchySchwarzMatrix<F>, cert: &Array2<N>) -> QFlag<N, F>
887where
888 N: Num + Clone + FromPrimitive,
889 F: Flag,
890{
891 let mut data = Vec::new();
892 for m in &cs.get() {
893 data.push(hadamard(cert, m));
894 }
895 QFlag {
896 basis: cs.1.output_basis(),
897 data: Array1::from(data),
898 scale: 1,
899 expr: Expr::unknown("Cauchy-Schwarz".into()),
900 }
901}
902
903pub fn cholesky_qflags<F>(cs: &CauchySchwarzMatrix<F>, mat: &Array2<f64>) -> Vec<QFlag<f64, F>>
904where
905 F: Flag,
906{
907 let mut res = Vec::with_capacity(mat.ncols());
908
909 let cholesky0 = Cholesky::cholesky(mat, UPLO::Upper).unwrap();
910 let cholesky = match cs.0 {
913 Invariant => {
914 let inv_mat = crate::density::class_matrices(&cs.1.invariant_classes().get())
915 .0
916 .map(|&x| x as f64);
917 (&inv_mat * &cholesky0.t()).t().to_owned()
918 }
919 AntiInvariant => {
920 let inv_mat = crate::density::class_matrices(&cs.1.invariant_classes().get())
921 .1
922 .map(|&x| x as f64);
923 (&inv_mat * &cholesky0.t()).t().to_owned()
924 }
925 _ => cholesky0,
926 };
927
928 for i in 0..cholesky.nrows() {
930 let data = cholesky.row(i).to_owned();
931 assert_eq!(data.len(), cs.1.split.left_basis().get().len());
932 res.push(QFlag {
933 basis: cs.1.split.left_basis(),
934 data,
935 scale: 1,
936 expr: Expr::unknown("Cholesky".into()),
937 })
938 }
939
940 res
941}
942
943pub fn eigenvectors_qflags<F>(
944 cs: &CauchySchwarzMatrix<F>,
945 mat: &Array2<f64>,
946) -> Vec<(f64, QFlag<f64, F>)>
947where
948 F: Flag,
949{
950 let mut res = Vec::with_capacity(mat.ncols());
952
953 let (eigenvalues, eigenvectors) = mat.eigh(UPLO::Lower).unwrap();
954
955 let eigenvectors = eigenvectors.t().to_owned();
956
957 let eigenvectors = match cs.0 {
958 Invariant => {
959 let inv_mat = crate::density::class_matrices(&cs.1.invariant_classes().get())
960 .0
961 .map(|&x| x as f64);
962 (&inv_mat * &eigenvectors.t()).t().to_owned()
963 }
964 AntiInvariant => {
965 let inv_mat = crate::density::class_matrices(&cs.1.invariant_classes().get())
966 .1
967 .map(|&x| x as f64);
968 (&inv_mat * &eigenvectors.t()).t().to_owned()
969 }
970 Simple => eigenvectors,
971 };
972
973 for i in 0..eigenvectors.nrows() {
974 let data = eigenvectors.row(i).to_owned();
975 assert_eq!(data.len(), cs.1.split.left_basis().get().len());
976 res.push((
977 eigenvalues[i],
978 QFlag {
979 basis: cs.1.split.left_basis(),
980 data,
981 scale: 1,
982 expr: Expr::unknown("Eigenvectors".into()),
983 },
984 ))
985 }
986
987 res
988}