flag_algebra/tools/
report.rs

1use self::ndarray_linalg::{Eigh, UPLO};
2use crate::algebra::*;
3use crate::expr::Names;
4use crate::flag::Flag;
5use crate::operator::{Basis, Savable};
6use crate::sdp::*;
7use crate::tools::Draw;
8use ndarray::ScalarOperand;
9use num::{FromPrimitive, Num, Zero};
10
11use sprs::CsMat;
12use std::fmt::Display;
13use std::fs::File;
14use std::io::*;
15use std::ops::AddAssign;
16use std::path::PathBuf;
17extern crate ndarray_linalg;
18use ndarray::Array1;
19use ndarray::Array2;
20
21/// Describe element in an html page
22pub trait Html {
23    fn print_html<W: Write>(&self, w: &mut W) -> Result<()>;
24
25    const LATEX: bool = false;
26    /// True if Mathjax need to be loaded
27
28    fn html(&self, name: &str) -> Result<()> {
29        let mut filename = PathBuf::from(name);
30        let _ = filename.set_extension("html");
31        let mut file = BufWriter::new(File::create(&filename)?);
32        writeln!(file, "<!DOCTYPE html><html><head><title>{name}</title>")?;
33        if Self::LATEX {
34            writeln!(file, "{MATHJAX}")?;
35        }
36        writeln!(file, "<style>{CSS}</style></head><body>")?;
37        self.print_html(&mut file)?;
38        writeln!(file, "</body>")
39    }
40}
41
42impl<F: Draw> Html for F {
43    fn print_html<W: Write>(&self, w: &mut W) -> Result<()> {
44        writeln!(w, "{}", self.draw())
45    }
46}
47
48const CSS: &str = "
49:root {
50    --color1: #a3bbdc;
51    --color2: #dae4f1;
52    --color3: #edf2f8;
53    --color4: #ffe8d6;
54    --darkcolor4: #ff971d;
55}
56h2 {
57    color: var(--darkcolor4);
58}
59body {
60    background-color: #133454;
61}
62.flags {
63    background-color: var(--color3);
64    margin: 5px 10px;
65}
66details[open] > summary {
67    background-color: var(--color1);
68}
69details {
70    background-color: var(--color2);
71}
72details[open] {
73    padding-bottom: 5px;
74    margin-bottom: 5px;
75}
76div.qflag_item {
77    display: inline-block;
78    margin: 10px;
79    text-align: center;
80}
81.qflag_item > span {
82    display: block;
83}
84div.inequality {
85    border-left-style:solid;
86    border-color: var(--darkcolor4);
87    border-width: thick;
88    background-color: var(--color4);
89    margin: 10px 0px 10px;
90    padding: 10px;
91}
92div.obj {
93    border-radius: 25px;
94    background-color: var(--color4);
95    margin: 10px 0px 10px;
96    padding: 10px;
97
98}
99svg.inline-flag {
100    width: 60px;
101    top: 20px;
102    position: relative;
103}
104";
105
106const MATHJAX: &str = "<script>
107MathJax = {
108  tex: {
109    inlineMath: ['\\\\[', '\\\\]', ['$','$']],
110  }
111};
112</script>
113     <script type=\"text/javascript\" id=\"MathJax-script\" async
114     src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js\">
115     </script>";
116
117fn print_tab<W, F, G, L>(w: &mut W, tab: &[G], mut f: F, type_size: usize) -> Result<()>
118where
119    W: Write,
120    F: FnMut(usize, &G) -> Option<L>,
121    G: Draw,
122    L: Display,
123{
124    writeln!(w, "<div class=\"flags\">")?;
125    for (i, x) in tab.iter().enumerate() {
126        if let Some(label) = f(i, x) {
127            writeln!(w, "<div class=\"qflag_item\">")?;
128            writeln!(
129                w,
130                "<span>{}</span>",
131                x.draw_with_parameters(|_| 0, type_size)
132            )?;
133            writeln!(w, "<span>{label}</span></div>")?;
134        }
135    }
136    writeln!(w, "</div>")
137}
138
139impl<G: Draw, F, L> Html for (&[G], F)
140where
141    F: FnMut(&G) -> L + Clone,
142    L: Display,
143{
144    const LATEX: bool = G::LATEX;
145
146    fn print_html<W: Write>(&self, w: &mut W) -> Result<()> {
147        let mut f = self.1.clone();
148        print_tab(w, self.0, |_, x| Some(f(x)), 0)?;
149        Ok(())
150    }
151}
152
153impl<F> Html for Basis<F>
154where
155    F: Flag + Draw,
156{
157    const LATEX: bool = F::LATEX;
158
159    fn print_html<W: Write>(&self, w: &mut W) -> Result<()> {
160        print_tab(w, &self.get(), |i, _| Some(i), self.t.size)
161    }
162}
163
164impl<F, N> Html for QFlag<N, F>
165where
166    F: Flag + Draw,
167    N: Num + Clone + Display + FromPrimitive,
168{
169    const LATEX: bool = F::LATEX;
170
171    fn print_html<W: Write>(&self, w: &mut W) -> Result<()> {
172        let scale: N = N::from_u64(self.scale).unwrap();
173        print_tab(
174            w,
175            &self.basis.get(),
176            |i, _| {
177                let val = self.data[i].clone();
178                if val.is_zero() {
179                    None
180                } else {
181                    Some(val / scale.clone())
182                }
183            },
184            self.basis.t.size,
185        )
186    }
187}
188
189fn inlined_list<'a, I>(iter: I) -> String
190where
191    I: Iterator + Clone + 'a,
192    I::Item: AsRef<str>,
193{
194    let mut res = String::new();
195    let last = iter.clone().count() - 1;
196    for (i, word) in iter.enumerate() {
197        if i > 0 {
198            if i == last {
199                res += " and ";
200            } else {
201                res += ", ";
202            }
203        };
204        res += word.as_ref();
205    }
206    res
207}
208
209impl<N, F> Html for Names<N, F>
210where
211    F: Flag + Draw,
212    N: Num + Clone + Display + FromPrimitive,
213{
214    const LATEX: bool = F::LATEX;
215
216    fn print_html<W: Write>(&self, w: &mut W) -> Result<()> {
217        // Inlined defintions
218        let mut inline_names = Vec::new();
219        for (t, name) in &self.types {
220            let svg = Basis::<F>::new(t.size).get()[t.id].draw_typed(t.size);
221            inline_names.push((name, svg))
222        }
223        for ((i, basis), name) in &self.flags {
224            let svg = basis.get()[*i].draw_typed(basis.t.size);
225            inline_names.push((name, svg))
226        }
227        if !inline_names.is_empty() {
228            let defs: Vec<_> = inline_names
229                .into_iter()
230                .map(|(name, svg)| format!("${}=$ {}", name, svg.set("class", "inline-flag")))
231                .collect();
232            writeln!(w, "<p>where {}.</p>", inlined_list(defs.iter()))?
233        }
234        // Long definitions
235        let other_names: Vec<_> = self
236            .sets
237            .iter()
238            .map(|(name, _, _)| name)
239            .chain(self.functions.iter().map(|(name, _)| name))
240            .map(|s| format!("${s}$"))
241            .collect();
242        if !other_names.is_empty() {
243            writeln!(
244                w,
245                "<details><summary>See the definition of {}.</summary>",
246                inlined_list(other_names.iter())
247            )?;
248            // Detailed content
249            for (name, basis, flags) in &self.sets {
250                writeln!(w, "<p>${name}$ contains the following flags:</p>")?;
251                writeln!(w, "<div class=\"flags\">")?;
252                for flag in flags {
253                    writeln!(w, "{}", flag.draw_typed(basis.t.size))?;
254                }
255                writeln!(w, "</div>")?;
256            }
257            for (name, qflag) in &self.functions {
258                writeln!(w, "<p>${name}$ takes the following values:</p>")?;
259                qflag.print_html(w)?;
260            }
261
262            writeln!(w, "</details>")?;
263        }
264        Ok(())
265    }
266}
267
268pub trait Approx: Clone + Zero {
269    fn is_negligible(&self) -> bool;
270    fn round(&self) -> Self {
271        if self.is_negligible() {
272            Self::zero()
273        } else {
274            self.clone()
275        }
276    }
277}
278
279impl Approx for f64 {
280    fn is_negligible(&self) -> bool {
281        self.abs() < 1e-8
282    }
283}
284
285fn round<N, F: Flag>(vec: &QFlag<N, F>) -> QFlag<N, F>
286where
287    N: Approx + Zero + Clone,
288{
289    vec.map(N::round)
290}
291
292// <script type=text/x-mathjax-config> MathJax.Hub.Config({tex2jax: {inlineMath: [['$','$']]}}); </script>
293
294fn header<W: Write>(w: &mut W, title: &str) -> Result<()> {
295    writeln!(
296        w,
297        "<!DOCTYPE html><html><head>
298<meta charset=\"UTF-8\">
299<title>{title}</title>
300{MATHJAX}
301<style>
302{CSS}
303</style></head><body>",
304    )
305}
306
307fn footer<W: Write>(w: &mut W) -> Result<()> {
308    writeln!(w, "</body></html>")
309}
310
311pub fn print_report<N, F>(
312    // /!\ select must be applied to pb only
313    pb: &ProblemView<N, F>,
314    cert: &Certificate<f64>,
315    filename: &str,
316) -> Result<()>
317where
318    F: Flag + Draw,
319    N: Display + Num + Clone + FromPrimitive + AddAssign + ScalarOperand + From<f64> + Approx,
320{
321    let mut filename = PathBuf::from(filename);
322    let _ = filename.set_extension("html");
323    let mut w = BufWriter::new(File::create(&filename)?);
324    // Header
325    header(&mut w, "Report")?;
326    // Objective
327    writeln!(w, "<h2>Objective:</h2>")?;
328    writeln!(w, "<div class=\"obj\">")?;
329    writeln!(w, "<p>Minimize: {}</p>", pb.obj.expr)?;
330    {
331        let mut names = Names::new();
332        writeln!(w, "<p>\\[{}\\]</p>", pb.obj.expr.latex(&mut names))?;
333        if !names.is_empty() {
334            names.print_html(&mut w)?;
335        }
336    }
337    writeln!(
338        w,
339        "<details><summary>Flag expression of the objective.</summary><div>"
340    )?;
341    round(pb.obj).print_html(&mut w)?;
342    writeln!(w, "</div></details>")?;
343    writeln!(w, "</div>")?;
344
345    writeln!(w, "<h2>Inequalities</h2>")?;
346
347    for (block, ineqs) in pb.ineqs.iter().enumerate() {
348        assert!(ineqs.len() > 0);
349        writeln!(w, "<div class=\"inequality\">")?;
350        writeln!(w, "<p>{:.6}</p>", ineqs.meta())?;
351        {
352            let mut names = Names::new();
353            writeln!(w, "<p>\\[{}\\]</p>", ineqs.meta().latex(&mut names))?;
354            names.print_html(&mut w)?;
355        }
356        write_diag_csmatrix(&mut w, &cert.x[block])?;
357        writeln!(w, "<details><summary>Contribution.</summary>")?;
358        let coeff: Vec<N> = cert
359            .diag_coeffs(block, ineqs.len())
360            .into_iter()
361            .map(|x| x.into())
362            .collect();
363        let (lhs, bound) = condense(ineqs, &coeff);
364        round(&lhs).print_html(&mut w)?;
365        writeln!(w, "\n is at least {bound}.</details>")?;
366        writeln!(w, "</div>")?;
367    }
368
369    writeln!(w, "<h2>Cauchy-Schwarz</h2>")?;
370
371    for (block, cs) in pb.cs.iter().enumerate() {
372        writeln!(w, "<div class=\"inequality\">")?;
373        writeln!(w, "<h4>{cs}</h4><p>")?;
374        svg::write(
375            &mut w,
376            &cs.1.unlabeling.basis.get()[cs.1.unlabeling.flag].draw(),
377        )?;
378        writeln!(w, "</p>")?;
379        let mat = &cert.x[pb.ineqs.len() + block];
380        write_csmatrix(&mut w, mat)?;
381        //
382        writeln!(w, "<details><summary>Eigenvalue decomposition</summary>")?;
383        let a: Array2<f64> = mat.to_dense();
384        let (eigenvalues, eigenvectors) = a.eigh(UPLO::Lower).unwrap();
385        write_array1(&mut w, &eigenvalues)?;
386        write_array2(&mut w, &eigenvectors)?;
387        writeln!(w, "</details>")?;
388        //
389        writeln!(w, "<details><summary>Eigenvectors</summary>")?;
390        for (lambda, vect) in &eigenvectors_qflags(&cs, &a) {
391            if !lambda.is_negligible() {
392                writeln!(w, "<h>λ = {lambda}</h><p>")?;
393                round(vect).print_html(&mut w)?;
394                writeln!(w, "</p>")?;
395            }
396        }
397        writeln!(w, "</details>")?;
398        //
399        writeln!(w, "<details><summary>Contribution.</summary>")?;
400        round(&condense_cs(&cs, &a)).print_html(&mut w)?;
401        writeln!(w, "</details>")?;
402        writeln!(w, "</div>")?;
403    }
404    footer(&mut w)
405}
406
407fn write_csmatrix<W: Write, N: Display>(w: &mut W, mat: &CsMat<N>) -> Result<()> {
408    writeln!(w, "<math><mfenced><mtable>")?;
409    for i in 0..mat.rows() {
410        writeln!(w, "<mtr>")?;
411        for j in 0..mat.cols() {
412            if let Some(x) = mat.get(i, j) {
413                writeln!(w, "<mtd>{x}</mtd>")?;
414            } else {
415                writeln!(w, "<mtd></mtd>")?;
416            }
417        }
418        writeln!(w, "</mtr>")?;
419    }
420    writeln!(w, "</mtable></mfenced></math>")
421}
422
423fn write_diag_csmatrix<W: Write, N: Display>(w: &mut W, mat: &CsMat<N>) -> Result<()> {
424    writeln!(w, "<math><mfenced><mtable><mtr>")?;
425    for i in 0..mat.rows() {
426        if let Some(x) = mat.get(i, i) {
427            writeln!(w, "<mtd>{x}</mtd>")?;
428        } else {
429            writeln!(w, "<mtd>0</mtd>")?;
430        }
431    }
432    writeln!(w, "</mtr></mtable></mfenced></math>")
433}
434
435fn write_array2<W: Write, N: Display>(w: &mut W, mat: &Array2<N>) -> Result<()> {
436    writeln!(w, "<math><mfenced><mtable>")?;
437    for i in 0..mat.nrows() {
438        writeln!(w, "<mtr>")?;
439        for j in 0..mat.ncols() {
440            writeln!(w, "<mtd>{}</mtd>", mat[(i, j)])?;
441        }
442        writeln!(w, "</mtr>")?;
443    }
444    writeln!(w, "</mtable></mfenced></math>")
445}
446
447fn write_array1<W: Write, N: Display>(w: &mut W, mat: &Array1<N>) -> Result<()> {
448    writeln!(w, "<math><mfenced><mtable>")?;
449    writeln!(w, "<mtr>")?;
450    for x in mat {
451        writeln!(w, "<mtd>{x}</mtd>")?;
452    }
453    writeln!(w, "</mtr>")?;
454    writeln!(w, "</mtable></mfenced></math>")
455}
456
457// For including quantum flags in latex reports
458pub fn latexify<F, N>(qflag: &QFlag<N, F>, folder: &str)
459where
460    F: Flag + Draw,
461    N: Num + Clone + Display + FromPrimitive,
462{
463    let mut path = PathBuf::from(folder);
464    let scale: N = N::from_u64(qflag.scale).unwrap();
465    for (i, (val, flag)) in qflag
466        .data
467        .iter()
468        .zip(qflag.basis.get().into_iter())
469        .enumerate()
470    {
471        if !val.is_zero() {
472            let b = qflag.basis;
473            let filename = format!("{}in{}t{}id{}", i, b.size, b.t.size, b.t.id);
474            path.push(&filename);
475            let _ = path.set_extension("svg");
476            svg::save(&path, &flag.draw_typed(qflag.basis.t.size)).unwrap();
477            let x = val.clone() / scale.clone();
478            if x.is_one() {
479                print!(" + \\flag{{{filename}}}")
480            } else {
481                print!(" + {val}\\cdot\\flag{{{filename}}}")
482            };
483            assert!(path.pop());
484        }
485    }
486}