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
21pub trait Html {
23 fn print_html<W: Write>(&self, w: &mut W) -> Result<()>;
24
25 const LATEX: bool = false;
26 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 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 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 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
292fn 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 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(&mut w, "Report")?;
326 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 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 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 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
457pub 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}