flag_algebra/
sdp.rs

1//! Create and manipulate semi-definite problems.
2
3use 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/// The optimization problems over flags are translated into a
28/// sdp problem in the sdpa format.
29///
30/// Shape of the matrices:
31///
32/// For each i in ineqs (where i is itself a vector of inequalities):
33///    A diagonal block of size i.len()
34///
35/// For each cs:
36///    A block with the size od `cs.input_matrix`
37
38/// An optimization problem expressed in flags algebra.
39#[derive(Debug, Clone)]
40pub struct Problem<N, F: Flag> {
41    /// Set of contraint inequalities.
42    pub ineqs: Vec<Ineq<N, F>>,
43    /// Set of Cauchy-Schwarz inequalities to be used.
44    pub cs: Vec<MulAndUnlabel<F>>,
45    /// Vector to be optimized.
46    pub obj: QFlag<N, F>,
47}
48
49// Write a line of a sdpa file
50fn 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
75// Write a coefficient `value` on position (i ,i) for an inequality,
76// or `value` and `-value` on positions (2i, 2i), (2i+1, 2i+1) for an equality
77fn 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    /// Create a minimization problem with the argument as objective function.
99    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    /// Panic if the size of the basis involved are inconsistent.
113    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    /// Write a description of the problem as a comment in the .sdpa file
155    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    /// Write the semi-definite program in the file `filename` in the sdpa format.
215    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        // Line 2: Number of blocks (one for each constraint)
225        writeln!(file, "{}", self.ineqs.len() + self.cs.len())?;
226        // Line 3: Sizes of the blocks
227        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        // Line 4: vector ai
236        // ai is the needed coefficient for the flag Fi
237        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        // Lines 5+: body
245        // Matrix 0: Objective
246        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        // Matrices 1+:
260        // Inequaltity blocks
261        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        // Cs blocks
277        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        // Cs subspace constraints
288        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    /// Write the semi-definite program in the file `filename` in the sdpa format.
307    pub fn write_sdpa(&self, filename: &str) -> io::Result<()> {
308        self.view(&Selector::new(self)).write_sdpa(filename)
309    }
310    /// Rescale the objective according to its scale field.
311    /// If this method is not used, the output of the sdp solver may need to be rescaled.
312    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    /// Solve the sdp using the CSDP solver.
320    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// Selectors
343
344/// An object that specify a subset of constraint of a flag problem.
345#[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/// Specifies a symmetry reduction for a product-and-unlabel matrix
386#[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/// Identifies a product-and-unlabel matrix with a symmetry reduction
398#[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/// A reference to a `Problem` filtered by a `Selector`.
434#[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
442// Sub-selector types
443type 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    /// Compute the number of group of inequalities
492    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    /// Number of (in)equalities in the selected group.
518    pub fn len(&self) -> usize {
519        self.selector.len()
520    }
521    /// Number of equalities in the selected group where equalities count for 2 (for ≥ and ≤).
522    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
535// Iterators
536impl<'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            // FIXME
544            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); //FIXME
593                             //simple.push(Invariant);
594        (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(), // Can be expensive
602        })
603        .variant_reduced(prob) // FIXME
604    }
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            // If the id is valid
680            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/// A certificate to a sdp problem as given by CSDP
690#[derive(Debug, Clone)]
691pub struct Certificate<N> {
692    pub y: Vec<N>,
693    pub z: Vec<CsMat<N>>, // matrix 1 in sdpa file
694    pub x: Vec<CsMat<N>>, // matrix 2 in sdpa file
695}
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        // The value of the vector y is on the first line
705        let y = buf
706            .next()
707            .unwrap()?
708            .split_whitespace()
709            .map(|x| x.parse::<f64>().unwrap())
710            .collect();
711        // Prepare the space for the matrices z and x
712        let mut tri_mat = [Vec::new(), Vec::new()];
713        for ineq in pb.ineqs.iter() {
714            let len = ineq.len_spliting_equalities(); // Bigger matrices to gather the coefficients
715            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            // We can avoid some memory usage here
722            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 the value is not on the diagonal, add the symmetric coeff
731            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        // condense inequality matrices
736        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        // convert the triplet matrices to sparse matrices
752        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(), // First matrix: z
758            x: sprs_mat.next().unwrap(), // Second matrix: x
759        })
760    }
761    pub fn to_file(&self, name: &str) -> io::Result<()> {
762        let mut w = BufWriter::new(File::create(name)?);
763        // The value of the vector y is on the first line
764        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    // C * X, here C is 0 for Cauchy-Schwarz inequalities
793    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 each block of inequalities
800        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    // Fixme
911
912    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    //FIXME
929    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    // FIXME: code duplication
951    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}