diff --git a/prace/bakalarka/bakalarka.py b/prace/bakalarka/bakalarka.py
index bf93fbb6689b068f80706a451e2786a48deef28e..9728dfe097757094a4b4f2d116550fe006b3f351 100644
--- a/prace/bakalarka/bakalarka.py
+++ b/prace/bakalarka/bakalarka.py
@@ -85,7 +85,12 @@ def main(build_dir=Path("build_pdf"), link_out=True):
         exec(code, globals)
         fig = globals["fig"]
 
-        fig.update_layout(margin = {'l':0,'r':0,'t':0,'b':0})
+        fig.update_layout(
+            margin = {'l':0,'r':0,'t':0,'b':0},
+            font=dict(
+                size=10,
+            ),
+        )
 
         file = get_name(element, context) + ".pdf"
 
@@ -101,7 +106,7 @@ def main(build_dir=Path("build_pdf"), link_out=True):
         code = element.text
         file = get_name(element, context) + ".pdf"
         r = sage.all.sage_eval("r", cmds=code)
-        r.save(build_dir/file, figsize=3)
+        r.save(str(build_dir/file), figsize=3)
         return img_from_source(element, context, processor, file)
 
     @formatitko_command
@@ -109,11 +114,13 @@ def main(build_dir=Path("build_pdf"), link_out=True):
         code = element.text
         file = get_name(element, context)
         with open(build_dir/(file+".asy"), "w") as f:
-            f.write('include "ksp.asy";\n')
+            f.write('include "jk_web.asy";\n')
             f.write(code)
         env = os.environ.copy()
-        env = os.environ.copy()
-        env["ASYMPTOTE_DIR"]=f".:{d}"
+        d_jk_web = Path("/".join(jk_web.__file__.split("/")[:-1]))
+        env["ASYMPTOTE_DIR"]=f".:{d_jk_web}"
+        d_formatitko = Path("/".join(formatitko.__file__.split("/")[:-1]))
+        env["TEXINPUTS"]=".:"+os.getcwd()+":"+str(d_formatitko/"tex")+":"+env.get("TEXINPUTS", "")
         subprocess.run(["asy", file+".asy", "-f", "pdf", "-tex", "pdftex"], check=True, cwd=build_dir, env=env)
 
         return img_from_source(element, context, processor, file+".pdf")
@@ -220,7 +227,6 @@ def main(build_dir=Path("build_pdf"), link_out=True):
     env = os.environ.copy()
     d_formatitko = Path("/".join(formatitko.__file__.split("/")[:-1]))
     env["TEXINPUTS"]=".:"+os.getcwd()+":"+str(d_formatitko/"tex")+":"+env.get("TEXINPUTS", "")
-    print(env["TEXINPUTS"])
     with open(build_dir/"toc.aux", "w") as f:
         pass
 
diff --git a/prace/bakalarka/formatitko_commands.py b/prace/bakalarka/formatitko_commands.py
index 54742264f55c008879873fa8ad204ff689847c30..26cb10b86c6186d7162528bab25dd6de86df36dc 100644
--- a/prace/bakalarka/formatitko_commands.py
+++ b/prace/bakalarka/formatitko_commands.py
@@ -84,8 +84,8 @@ def cmt(element, content, processor):
 def figure(element, content, processor):
     data = element.content[:-1]
     caption = element.content[-1].content
-    x = processor.transform([pf.Figure(*data, caption=pf.Caption(pf.Plain(*caption)), identifier=element.identifier, attributes=element.attributes)])
-    x[0].content = remove_div_para_to_plain(*x[0].content)
+    fig = pf.Figure(*data, caption=pf.Caption(pf.Plain(*caption)), identifier=element.identifier, attributes=element.attributes)
+    x = processor.transform([fig])
     return x
 
 def copy(e, *content):
diff --git a/prace/bakalarka/g.py b/prace/bakalarka/g.py
index e7909a105ae0a55e5b374013bc60563d0b7b4767..236a59f0db5df18cf6b40d93f928b83b749fd33e 100644
--- a/prace/bakalarka/g.py
+++ b/prace/bakalarka/g.py
@@ -33,16 +33,17 @@ def draw_algo_graph(fig, data, algo, n, val_getter=lambda x:x.score, name=None,
     for i in d:
         y[val_getter(i)] += 1
     fig.add_trace(go.Histogram(x=[val_getter(i) for i in d], name=name or algo_to_name[algo], xbins=dict(size=1), marker=dict(color=color or algo_to_color[algo])))
-    fig.add_trace(go.Box(x=[val_getter(i) for i in d], name=name or algo_to_name[algo], showlegend=False, marker=dict(color=color or algo_to_color[algo]), boxmean=True), row=2, col=1)
+    fig.add_trace(go.Box(x=[val_getter(i) for i in d], name="", showlegend=False, marker=dict(color=color or algo_to_color[algo]), boxmean=True), row=2, col=1)
     fig.update_xaxes(range=[0, 2*n])
     # xaxis=np.arange(0, 2*n)
-    fig.update_layout(yaxis_title="Počet řešení")
-    fig.update_xaxes(title_text="Skóre", row=2, col=1)
+    fig.update_layout(yaxis_title="Počet řešení.")
+    fig.update_xaxes(title_text="Skóre.", row=2, col=1)
 
 
 def intro_graph(algo):
     fig = plotly.subplots.make_subplots(rows=2, cols=1, row_heights=[0.8, 0.2], shared_xaxes=True, vertical_spacing = 0.05)
     draw_algo_graph(fig, load_main_test(), algo, 200)
+    fig.update_layout(showlegend=False)
     return fig
 
 def nonzero_coords(n, seeds, max_dim=20):
@@ -59,6 +60,8 @@ def nonzero_coords(n, seeds, max_dim=20):
         )])
 
     fig.update_layout(
+        xaxis_title="Index souřadnice.",
+        yaxis_title="Počet bodů s hodnotou dané souřadnice ≥ 0.05.",
         xaxis=dict(showgrid=False),
         yaxis=dict(range=[0, n]),
         showlegend=False
@@ -79,6 +82,8 @@ def max_coords(n, seeds, max_dim=20):
         )])
 
     fig.update_layout(
+        xaxis_title="Index souřadnice.",
+        yaxis_title="Max. hodnota dané souřadnice všech bodů řešení.",
         xaxis=dict(showgrid=False),
         yaxis=dict(range=[0, 1]),
         showlegend=False
diff --git a/prace/bakalarka/index.md b/prace/bakalarka/index.md
index 3b1aae778bee7ea2f73ad0e74946acf8c7134d2a..6dd626906e443fc60b66a42ccdc272c1bfe805fa 100755
--- a/prace/bakalarka/index.md
+++ b/prace/bakalarka/index.md
@@ -44,19 +44,17 @@ ft:
             We proved that for each input this algorithm returns a solution with expected deviation from optimum of at most 0.212 times the number of car types.
 ---
 ``` {c=cmt}
-Obrázek koule
-
 Klikaci věci + generování obsahu formátítkem
 Algoritmy velké písmena
-Popisky obrázků
-Popisky os
 Obsah bez změn velikostí
 barevné schéma grafu
-font grafů
-Velikost poznámky pod čárou
 
-složitost je n^c pro jaké c?
 Absolutní číslování ve struktuře
+
+Vizualizace: více pohledů a správné pořadí hran.
+Jednotná TeXlib pro asymptote
+
+Velikost \vec
 ```
 
 ::: {only=html}
@@ -73,15 +71,8 @@ $\def\progline#1#2#3{\hbox{#1}\hskip 1cm\relax #2 \hskip 1cm\relax #3}
 \protected\def\P{{\rm P}}
 \protected\def\NP{{\rm NP}}
 \protected\def\APX{{\rm APX}}
-\protected\def\vec#1{\overrightarrow{#1\,}}
+\protected\def\vec#1{\vecoverrightarrow{#1}}
 \let\rightarrowfillreal\rightarrowfill
-\catcode`@=11
-\def\overrightarrow#1{\vbox{\m@th\ialign{##\crcr
-      \rightarrowfill\crcr\noalign{\kern-\p@\kern 0.09em\nointerlineskip}
-      $\hfil\displaystyle{#1}\hfil$\crcr}}}
-\def\rightarrowfill{$\settextsize{5}\m@th\smash-\mkern-7mu%
-  \cleaders\hbox{$\mkern-2mu\smash-\mkern-2mu$}\hfill
-  \settextsize{5}\mkern-7mu\mathord\rightarrow$}
 \protected\def\opt{{\rm opt}}
 \protected\def\algo#1{{\bf #1}}
 \protected\def\alg{\algo{alg}}
@@ -287,13 +278,15 @@ fig = go.Figure(data=[go.Scatter(
     mode = 'markers',
     )])
 fig.update_layout(
-    xaxis_title="n",
+    xaxis_title="Počet typů aut.",
+    yaxis_title="Relativní skóre.",
     xaxis=dict(range=[0,None]),
     yaxis=dict(range=[0, 2]),
-    showlegend=False
+    showlegend=False,
 )
 ```
-Graf střední hodnoty relativního skóre hladového řešení $\delta_{\algo g}(n)$ v závislosti na $n$.
+
+Graf střední hodnoty rel. skóre hladového řešení $\delta_{\algo g}(n)$ v závislosti na $n$.
 :::
 
 U hladového řešení je tedy střední hodnota skóre přes uniformně náhodný vstup přesně vyčíslená.
@@ -442,12 +435,9 @@ Auta pro nás budou drahokamy. Barva auta bude označovat, který loupežník z
 Minimalizovat počet intervalů je to stejné jako minimalizovat počet hranic mezi nimi (kterých je o jedna méně něž intervalů).
 Ve vstupu problému dělení náhrdelníku navíc musí platit, pro každý typ má být stejný počet aut obarvený jednotlivými barvami
 (a tedy počet aut daného typu musí být násobkem $k$).
-
 Naopak binární paint shop problém je speciálním případem dělení náhrdelníku
 pro dva lupiče, kde navíc platí, že všechna $a_i$ jsou 1 (tedy od každého drahokamu jsou na náhrdelníku právě dva drahokamy).
 
-V této práci se budeme věnovat pouze binárnímu paint shop problému.
-
 Semidefinitní programování
 ==========================
 
@@ -597,10 +587,10 @@ Jednotkový vektor můžeme generovat tak, že vygenerujeme náhodný vektor nez
 Povšimne si, že normalizace ani není potřeba,
 protože to na znaménku součinů nic nemění.
 
-Nyní pojďme precizněji spočítat pravděpodobnost toho, že se dvojice bodů (vektorů) $\vec{u}$,
-$\vec{v}$ rozdělí náhodnou nadrovinou $\rho$ v závislosti na hodnotě skalárního
+Nyní pojďme precizněji spočítat pravděpodobnost toho, že se dvojice bodů (vektorů) $\vec{y_u}$,
+$\vec{y_v}$ rozdělí náhodnou nadrovinou $\rho$ v závislosti na hodnotě skalárního
 součinu mezi nimi.
-Můžeme se podívat na rovinu, ve které leží počátek a oba vektory $\vec{u}$, $\vec{v}$ (viz obrázek [](#gw-cut)).
+Můžeme se podívat na rovinu, ve které leží počátek a oba vektory $\vec{y_u}$, $\vec{y_v}$ (viz obrázek [](#gw-cut)).
 Průnik náhodné nadroviny s touto rovinou tvoří rovnoměrně náhodně vybranou přímku procházející počátkem.
 
 ::: {#gw-cut c=figure}
@@ -612,49 +602,63 @@ label("$\rho$", (-2,-1), N);
 pair u = rotate(70)*(1,0);
 pair v = rotate(-15)*(1,0);
 draw((0,0)--u,Arrow);
-label("$\vec{u}$", u, N);
+label("$\vecoverrightarrow{y_u}$", u, N);
 draw((0,0)--v,Arrow);
-label("$\vec{v}$", v, E);
+label("$\vecoverrightarrow{y_v}$", v, E);
 draw(scale(0.2)*rotate(-15)*subpath(unitcircle, 0, 85/90));
 label("$\alpha$", (0.2, 0), 0.7*ENE);
 ```
-Znázornění řezu rovinou obsahující $\vec{u}$ i $\vec{v}$.
+Znázornění řezu rovinou obsahující $\vec{y_u}$ i $\vec{y_v}$.
 :::
 
 
 Zajímá nás tedy, jaká je pravděpodobnost toho, že jedno z ramen nadroviny bude uvnitř konvexního úhlu
-mezi $\vec{u}$ a $\vec{v}$.
+mezi $\vec{y_u}$ a $\vec{y_v}$.
 Velikost tohoto úhlu označme $\alpha$.
-Víme, že $\vec{u}^{\rm\, T}\vec{v} = 1 \cdot 1 \cdot \cos a$.
+Víme, že $\vec{y_u}^{\rm\, T}\vec{y_v} = 1 \cdot 1 \cdot \cos \alpha$.
 Náhodnou přímku můžeme vygenerovat jako náhodný úhel $\beta$ mezi $0$ a $2\pi$ s tím, že
 přímka pak povede směrem $\beta$ a $(\beta + \pi) \mod 2\pi$. Nikdy však uvnitř konvexního úhlu nebudou obě
 ramena přímky, tedy pravděpodobnost, že alespoň jedno bude uvnitř a tedy body budou oddělené je:
 
-$$ \frac{2 \cdot \alpha}{2\pi} = \frac{\alpha}{\pi} = \frac{\arccos \vec{u}^{\rm\,T}\vec{v}}{\pi}$$
+$$ \frac{2 \cdot \alpha}{2\pi} = \frac{\alpha}{\pi} = \frac{\arccos \vec{y_u}^{\rm\,T}\vec{y_v}}{\pi}$$
 
 U každé hrany $uv$ optimalizujeme $-h(u,v) \vec{y_u}^{\rm T}\vec{y_v}$, což je ekvivalentní
 optimalizování $h(u,v)\cdot\left(\frac{1}{2} - \frac{\vec{y_u}^{\rm T}\vec{y_v}}{2}\right)$.
 
 ::: {#gw-func c=figure}
-```python {c=sage}
-x = var('x')
-r = plot([1/2 - x/2, arccos(x)/pi], xmin=-1, xmax=1)
+```asy {c=asy}
+import graph;
+size(380,180,IgnoreAspect);
+real f(real x) {return 1/2-x/2;}
+real g(real x) {return acos(x)/pi;}
+draw(graph(f, -1, 1),red,"\vbox{\hbox{Pravděpodobnost řezu}\vskip 2pt\hbox{$\frac{1 - \vecoverrightarrow{y_u}^{\rm T}\vecoverrightarrow{y_v}}{2}$}}");
+real g(real x) {return acos(x)/pi;}
+draw(graph(g, -1, 1),blue,"\vbox{\hbox{Účelová funkce}\vskip 2pt\hbox{$\frac{\arccos \vecoverrightarrow{y_u}^{\rm\,T} \vecoverrightarrow{y_v}}{\pi}$}}");
+xaxis("$\vecoverrightarrow{y_u}^{\rm T}\vecoverrightarrow{y_v}$",BottomTop,LeftTicks);
+yaxis(LeftRight,RightTicks);
+add(legend(linelength=15pt),point(E),10E);
 ```
 Graf funkcí pravděpodobnosti řezu a účelové funkce.
 :::
 
-Nyní se podívejme na poměr mezi pravděpodobností toho, že hrana bude v řezu, a účelovou funkcí před vynásobením hodnotou hrany jakožto funkci proměnné $x = \vec{u}^{\rm\,T} \vec{v}$ v intervalu $\left[ -1, 1 \right]$:
+Nyní se podívejme na poměr mezi pravděpodobností toho, že hrana bude v řezu, a účelovou funkcí před vynásobením hodnotou hrany jakožto funkci proměnné $x = \vec{y_u}^{\rm\,T} \vec{y_v}$ v intervalu $\left[ -1, 1 \right]$:
 
 $$
-\frac{\arccos \vec{u}^{\rm\,T} \vec{v}}{\pi} / \frac{1 - \vec{y_u}^{\rm T}\vec{y_v}}{2}
+\frac{\arccos \vec{y_u}^{\rm\,T} \vec{y_v}}{\pi} / \frac{1 - \vec{y_u}^{\rm T}\vec{y_v}}{2}
 =
 \frac{\arccos x}{\pi} / \frac{1 - x}{2}
 $$
 
 ::: {#gw-frac c=figure}
-```python {c=sage}
-x = var('x')
-r = plot(arccos(x)/pi / (1/2 - x/2), xmin=-1, xmax=1, ymin=0, ymax=5)
+```asy {c=asy}
+import graph;
+size(350,180,IgnoreAspect);
+real f(real x) {return acos(x)/pi/(1/2-x/2);}
+draw(graph(f, -1, 0.99),red,"$\frac{\frac{\arccos \vecoverrightarrow{y_u}^{\rm\,T} \vecoverrightarrow{y_v}}{\pi}}{\frac{1 - \vecoverrightarrow{y_u}^{\rm T}\vecoverrightarrow{y_v}}{2}}$");
+ylimits(0,2, Crop);
+xaxis("$\vecoverrightarrow{y_u}^{\rm T}\vecoverrightarrow{y_v}$",BottomTop,LeftTicks, xmax=1);
+yaxis(LeftRight,RightTicks);
+add(legend(linelength=15pt),point(E),10E);
 ```
 Graf poměru pravděpodobnosti řezu a účelové funkce.
 :::
@@ -753,9 +757,15 @@ postupu by nás ani neměla zaskočit, protože v případě, že by šlo uděla
 Místo toho se podíváme na rozdíl pravděpodobnosti a účelové funkce.
 
 ::: {#sdp-diff c=figure}
-```python {c=sage}
-x = var('x')
-r = plot(arccos(x)/pi - (1/2 - x/2), xmin=-1, xmax=1)
+```asy {c=asy}
+import graph;
+size(400,180,IgnoreAspect);
+real f(real x) {return acos(x)/pi - (1/2-x/2);}
+draw(graph(f, -1, 1),red,"$\frac{\arccos \vecoverrightarrow{u}^{\rm\,T} \vecoverrightarrow{v}}{\pi} - \frac{1 - \vecoverrightarrow{y_u}^{\rm T}\vecoverrightarrow{y_v}}{2}$");
+ylimits(-0.2,0.2, Crop);
+xaxis("$\vecoverrightarrow{x_i}^{\rm T}\vecoverrightarrow{y_i}$",BottomTop,LeftTicks, xmax=1);
+yaxis(LeftRight,RightTicks);
+add(legend(linelength=15pt),point(E),10E);
 ```
 Graf rozdílu pravděpodobnosti řezu a účelové funkce.
 :::
@@ -771,9 +781,17 @@ Zderivováním získáme:
 $$f'(x) = - \frac{1}{\pi \sqrt{-x^2 +1}} + \frac12 ,$$
 
 ::: {#sdp-derivative c=figure}
-```python {c=sage}
-x = var('x')
-r = plot(derivative(arccos(x)/pi - (1/2 - x/2)), xmin=-1, xmax=1, ymin=-1, ymax=0.3)
+```asy {c=asy}
+import graph;
+size(350,180,IgnoreAspect);
+real f(real x) {return -1/(pi*sqrt(-x*x+1))+ 1/2;}
+real g(real x) {return 0;}
+draw(graph(f, -0.99, 0.99),red,"$f'(x)$");
+draw(graph(g, -1, 1));
+ylimits(-0.5,0.25, Crop);
+xaxis("$\vecoverrightarrow{x_i}^{\rm T}\vecoverrightarrow{y_i}$",BottomTop,LeftTicks, xmin=-1, xmax=1);
+yaxis(LeftRight,RightTicks);
+add(legend(linelength=15pt),point(E),10E);
 ```
 Derivace funkce $f$.
 :::
@@ -829,13 +847,13 @@ Měření řešení BPS
 
 Součástí práce je implementace algoritmů řešících Binární paint shop problém.
 Každý z nich byl následně spuštěn pro různé velikosti, pokaždé na 100 nezávisle náhodně vybraných vstupech s počtem typů aut
-10, 20, 50, 100, 200, 400, 566, 800, 1131, 1600, 2263 a 3200 (jedna z implementací $\algo{sdp}$ vyžaduje moc paměti a proto byla spuštěna jen na vstupech do velikosti $566$).
+10, 20, 50, 100, 200, 400, 566, 800, 1131, 1600, 2263 a 3200 (jedna z implementací $\algo{sdp}$ -- pomocí sage vyžaduje moc paměti a proto byla spuštěna jen na vstupech do velikosti $566$).
 Celý test běžel jednovláknově zhruba dva dny a využíval nejvýše 16 GB operační paměti.
 U $\algo{sdp}$ řešení bylo použito 10 náhodných řezů a vždy byl vybraný nejlepší z nich.
-Gitový repozitář s implementací i naměřenými daty je dostupný na <https://gitlab.kam.mff.cuni.cz/jirikalvoda/binary-paint-shop-problem>.
+Gitový repozitář s implementací i naměřenými daty je k dispozici na <https://gitlab.kam.mff.cuni.cz/jirikalvoda/binary-paint-shop-problem>.
 Na následujících stranách jsou zpracovaná různá naměřená data.
 
-Pro reprodukovatelnost vygenerovaných řešení byly vstupy generovány ze seedů.
+Pro reprodukovatelnost byly vstupy generovány deterministicky ze seedů.
 Se stejným seedem se tedy vždy vygeneruje stejný vstup a ideálně i stejné řešení.
 Pro různé algoritmy a velikosti vstupů byly použité různé seedy.
 
@@ -962,14 +980,15 @@ d = pathlib.Path("/".join(__file__.split("/")[:-1]))
 
 fig = plotly.subplots.make_subplots(rows=2, cols=1, row_heights=[0.8, 0.5], shared_xaxes=True, vertical_spacing = 0.05)
 scalar_products = sum(([float(i) for i in open(d/f"semidef_prog_scalar_products/n200_seed{i:04}").read().split()] for i in range(1,101)), start=[])
-fig.add_trace(go.Histogram(x=scalar_products, name="Hodnoty skalárních součinů", xbins=dict(size=0.01), marker=dict(color="blue")))
+fig.add_trace(go.Histogram(x=scalar_products, name="Naměřený počet výskytů", xbins=dict(size=0.01), marker=dict(color="blue")))
 x_vals = [i/500 for i in range(-500, 500+1)]
 fig.add_trace(go.Scatter(x=x_vals, y=[math.acos(x)/math.pi - (1/2 - x/2) for x in x_vals], mode='lines', name="Rozdíl pravděpodobnosti řezu a účelové funkce"), row=2, col=1)
 fig.update_xaxes(range=[-1, 1])
 fig.update_layout(
-    legend=dict(
-        orientation="h",
-        ),
+    xaxis2_title="Hodnota skalárního součinu vektorů sousedních aut.",
+    xaxis2_title_standoff = 0,
+    margin_b = 15,
+    legend=dict(orientation="h"),
 )
 ```
 Distribuce skalárních součinů $100$ běhů algoritmu pro $n=200$.
@@ -1003,6 +1022,27 @@ všechny vektory v něm mají několik prvních souřadnic velké hodnoty
 a ve zbylých souřadnic mají hodnoty blízké nule.
 Tedy kdybychom vektory promítli na méně dimenzionální sféru (vzali místo nich nejbližší bod na ní), tak se účelová funkce moc nezmění.
 
+
+Nevíme o žádné hypotéze, proč program generuje řešení s malou dimenzí.
+Taktéž není jasné, jestli či jak by toho šlo využit pro získání lepšího řešení či dolního odhadu.
+Z naměřených dat zobrazených na obrázcích [](#max_coords_400) až [](#nonzero_coords_50) na následujících stranách však vychází, že průměrná dimenze je mnohem menší než $n$, ovšem nejspíše není omezena žádnou konstantou, tedy s rostoucím $n$ také roste, ale mnohem pomaleji.
+Hypotéza zní, že průměrná dimenze leží v $\Theta(\log n)$.
+
+Malá dimenze má ale zásadní výhodu pro vizualizaci řešení, protože trojrozměrný prostor (tedy sféru dimenze 2) si zvládne člověk snadno představit. Z čehož plyne, že zhruba polovinu řešení pro $n=50$ jsme schopni
+zakreslit jen s drobným zkreslením, tak, jak je ukázáno na obrázku [](#sdp-visualize_50).
+
+::: {#sdp-visualize_50 c=figure}
+```python {.run}
+from bakalarka import sdp_visualize
+return processor.transform([pf.CodeBlock(sdp_visualize.visualize("semidef_prog_n50_seed2"), attributes=dict(c="asy"))])
+```
+
+Světlá barva bodu značí bod na zadní straně sféry.
+Černé čáry spojují sousední auta a šedé jsou jim středově simetrické (protože dvojice aut, jejichž druhá auta stejného typu jsou vedle sebe, je také přitahována k sobě).
+
+Vizualizace jednoho z řešení pro $n=50$, které se vejde do 3D.
+:::
+
 ::: {#max_coords_400 c=figure floatpage=400}
 ```python {c=plotly}
 from bakalarka import g
@@ -1068,23 +1108,6 @@ fig = g.nonzero_coords(50, range(2001, 2101))
 Počet vektorů s danou souřadnicí větší než $0.05$ pro $n=50$.
 :::
 
-Nevíme o žádné hypotéze, proč program generuje řešení s malou dimenzí.
-Taktéž není jasné, jestli či jak by toho šlo využit pro získání lepšího řešení či dolního odhadu.
-Z naměřených dat zobrazených na obrázcích [](#max_coords_400) až [](#nonzero_coords_50) na následujících stranách však vychází, že průměrná dimenze je mnohem menší než $n$, ovšem nejspíše není omezena žádnou konstantou, tedy s rostoucím $n$ také roste, ale mnohem pomaleji.
-Hypotéza zní, že průměrná dimenze leží v $\Theta(\log n)$.
-
-Malá dimenze má ale zásadní výhodu pro vizualizaci řešení, protože trojrozměrný prostor (tedy sféru dimenze 2) si zvládne člověk snadno představit. Z čehož plyne, že zhruba polovinu řešení pro $n=50$ jsme schopni
-zakreslit jen s drobným zkreslením, tak, jak je ukázáno na obrázku [](#sdp-visualize_50).
-
-::: {#sdp-visualize_50 c=figure}
-```python {.run}
-from bakalarka import sdp_visualize
-return processor.transform([pf.CodeBlock(sdp_visualize.visualize("semidef_prog_n50_seed2"), attributes=dict(c="asy"))])
-```
-
-Vizualizace jednoho z řešení pro $n=50$, které se vejde do 3D.
-:::
-
 \vfil\supereject
 
 ## Časová složitost algoritmu $\algo{sdp}$
@@ -1100,21 +1123,36 @@ Během výpočtu na stoji neběželo nic kromě výpočtu a základních funkcí
 
 ::: {#tmp2 c=figure}
 ```python {c=plotly}
+from math import log, exp
 from bakalarka import g, data_lib
+from sklearn.linear_model import LinearRegression
+
+def gen(d, name, minim, color, color_line):
+    pred_x = list(range(minim, max(i.n for i in d)+1))
+    regr = LinearRegression()
+    regr_res = regr.fit([[log(i.n)] for i in d if i.resources_cpu_time_s and i.n>minim], [log(i.resources_cpu_time_s) for i in d if i.resources_cpu_time_s and i.n>minim])
+    predict = [exp(i) for i in regr.predict([[log(i)] for i in pred_x])]
+    return [
+        go.Box(
+            x=[i.n for i in d],
+            y=[i.resources_cpu_time_s for i in d],
+            name=name,
+            marker_color=color,
+        ),
+        go.Scatter(x=pred_x, y=predict, mode="lines", marker_color=color_line, name=f"n^{regr.coef_[0]:.3} * {exp(regr.intercept_):.1g}".replace("-0", "-")),
+    ]
+
 
 data = g.load_main_test()
-d = data_lib.group_by_n(data.pipelines["semidef_prog_sage.sage(10, CVXOPT)"])
 
-fig = go.Figure(data=[go.Box(
-    x=[i.n for i in d],
-    y=[i.resources_cpu_time_s for i in d],
-    name=name
-    ) for d,name in [
-        [data.pipelines["semidef_prog(10)"], "SDPA-C"],
-        [data.pipelines["semidef_prog_sage.sage(10, CVXOPT)"], "Sage"],
-    ]])
+fig = go.Figure(data=[
+    *gen(data.pipelines["semidef_prog(10)"], "SDPA-C", 400, "blue", "lightblue"),
+    *gen(data.pipelines["semidef_prog_sage.sage(10, CVXOPT)"], "Sage", 100, "red", "pink"),
+    ])
 
 fig.update_layout(
+    xaxis_title="Počet typů aut (logaritmická stupnice)",
+    yaxis_title="Čas běhu v sekundách (logaritmická stupnice)",
     xaxis=dict(showgrid=False, type="log"),
     yaxis=dict(type="log"),
 )
@@ -1122,7 +1160,8 @@ fig.update_layout(
 Závislost času řešení na velikosti vstupu.
 :::
 
-TODO proložit přímkou a udělat závěr.
+Dle výše uvedeného grafu můžeme usuzovat, že časová složitost obou algoritmů je v $\O(n^4) \cap \Omega(n^3)$.
+Implementace pomocí SDPA-C má menší multiplikativní konstantu a navíc mnohem rychlejší čas startu, který se zejména projevuje na malých instancích.
 
 \vfil\supereject
 
@@ -1158,6 +1197,8 @@ fig.add_hline(y=0.34,
               annotation_text="0.34", annotation_position="top left",
               fillcolor="green")
 fig.update_layout(
+    xaxis_title="Počet typů aut (logaritmická stupnice)",
+    yaxis_title="Relativní skóre",
     xaxis=dict(showgrid=False, type="log"),
     yaxis=dict(range=[0, 1]),
 )
@@ -1213,6 +1254,8 @@ fig.add_hline(y=0.214,
               annotation_text="0.214", annotation_position="top left",
               fillcolor="green")
 fig.update_layout(
+    xaxis_title="Počet typů aut (logaritmická stupnice)",
+    yaxis_title="Relativní skóre",
     xaxis=dict(showgrid=False, type="log"),
     yaxis=dict(range=[0, 1]),
 )
diff --git a/prace/bakalarka/ksp.asy b/prace/bakalarka/ksp.asy
deleted file mode 100644
index 16f634b2247e793027b2c5d7510e833852a57713..0000000000000000000000000000000000000000
--- a/prace/bakalarka/ksp.asy
+++ /dev/null
@@ -1,56 +0,0 @@
-import settings;
-outformat="pdf";
-
-settings.texcommand = "luatex";
-texpreamble("
-\input luatex85.sty
-\input ucwmac2.tex
-\input ltluatex.tex
-\ucwmodule{luaofs}
-\ucwmodule{verb}
-\ucwmodule{link}
-\clickablefalse
-
-% Fonty
-\ofsdeclarefamily [Pagella] {%
-   \loadtextfam qplr;%
-                qplb;%
-                qpli;%
-                qplbi;;%
-}
-
-\def\MSfeat#1{:mode=node;script=latn;+tlig}
-\registertfm qplr      -      file:texgyrepagella-regular.otf\MSfeat{}
-\registertfm qplb      -      file:texgyrepagella-bold.otf\MSfeat{}
-\registertfm qpli      -      file:texgyrepagella-italic.otf\MSfeat{}
-\registertfm qplbi     -      file:texgyrepagella-bolditalic.otf\MSfeat{}
-
-\settextsize{12}
-
-\uselanguage{czech}\frenchspacing\lefthyphenmin=2\righthyphenmin=2{}
-
-\input glyphtounicode.tex
-\pdfgentounicode=1
-");
-
-defaultpen(fontcommand("\relax"));
-
-pen ksp_pen[] = {
-	linewidth(0.15pt),
-	linewidth(0.4pt),
-	linewidth(0.8pt),
-	linewidth(1.2pt),
-};
-
-pen ksp_thin_pen = ksp_pen[0];
-pen ksp_normal_pen = ksp_pen[1];
-pen ksp_thick_pen = ksp_pen[2];
-pen ksp_fat_pen = ksp_pen[3];
-
-defaultpen(ksp_normal_pen);
-
-pen ksp_short_dashed = linetype(new real[] { 1, 4 });
-pen ksp_mid_dashed = linetype(new real[] { 4, 4 });
-pen ksp_fine_dotted = ksp_thin_pen + 0.3mm + linetype(new real[] { 0, 2.5 });
-pen ksp_gray = gray(0.6);
-pen ksp_area_gray = gray(0.75);
diff --git a/prace/bakalarka/sdp_visualize.py b/prace/bakalarka/sdp_visualize.py
index 39024c1dcc728fd4b54158e09ae3f01e6a8df049..500eed45e27eb4617ef88da4789571213fcef381 100644
--- a/prace/bakalarka/sdp_visualize.py
+++ b/prace/bakalarka/sdp_visualize.py
@@ -1,20 +1,44 @@
 import pathlib
-import numpy as np
 d = pathlib.Path("/".join(__file__.split("/")[:-1]))
 
+from scipy.spatial.transform import Rotation
+import numpy as np
+from numpy.linalg import norm
+
+
+
+
 def visualize(filename):
     def car_place(i, sgn=1):
         coord = [x*sign[i]*sgn for x in cholesky[inp[i]]]
         return place(coord)
     def place(coord):
-        coord = np.array(coord)
+        coord = np.array(coord[:3])
+        coord = coord / norm(coord)
+        return rot.apply(coord)
+    def format_place(coord):
         return f"({coord[0]}, {coord[1]})"
+
+    def f_car_place(*args, **kwargs):
+        return format_place(car_place(*args, **kwargs))
+
+    def is_front_car(i):
+        return car_place(i)[2] > 0
+
+
+    def car_color(i):
+        return "blue" if sum(a*b*sign[i] for a,b in zip(cholesky[inp[i]], cut)) > 0 else "red"
+
     with open(d/filename) as f:
         data = f.read().split("\n")
         inp = list(map(int, data[0].split()))
         n = len(inp)//2
         cholesky = [list(map(float, i.split())) for i in data[1:n+1]]
         cut = list(map(float, data[n+2].split())) 
+    rot = Rotation.align_vectors([(1,0,0)], [cut])[0]
+    print(rot)
+    print(cut)
+    print(rot.apply(cut))
 
     for i in cholesky:
         assert sum(x**2 for x in i) > 0.99, f"Dimension higher then input ({sum(x**2 for x in i)})"
@@ -25,8 +49,28 @@ def visualize(filename):
         type_sum[inp[i]] += i
     sign = [-1 if type_sum[inp[i]] > 2*i else 1 for i in range(2*n)]
     out.append("unitsize(80);")
+    out.append("""
+    path edge_path(pair a, pair b)
+{
+    path pa = a--b;
+	real x = intersections(pa,circle(a, 0.017))[0][0];
+	real y = intersections(pa,circle(b, 0.017)).pop()[0];
+	return subpath(pa,x,y);
+}
+   """)
+
+    for i in range(2*n):
+        if not is_front_car(i):
+            out.append(f"filldraw(circle({f_car_place(i)},0.015), light{car_color(i)});")
+    for i in range(2*n-1):
+        out.append(f"draw(edge_path({f_car_place(i, -1)}, {f_car_place(i+1, -1)}), gray(0.8));")
     for i in range(2*n-1):
-        out.append(f"draw({car_place(i)} -- {car_place(i+1)});")
-        out.append(f"draw({car_place(i, -1)} -- {car_place(i+1, -1)}, green);")
-    out.append(f"filldraw(circle({place(cut)},0.01));")
+        out.append(f"draw(edge_path({f_car_place(i)}, {f_car_place(i+1)}));")
+    out.append(f"draw(circle((0,0),1));")
+    out.append(f"draw((0,-1.1)-- (0, 1.1));")
+    for i in range(2*n):
+        if is_front_car(i):
+            out.append(f"filldraw(circle({f_car_place(i)},0.015), {car_color(i)});")
+    # out.append(f"filldraw(circle({format_place(place(cut))},0.01));")
+
     return "\n".join(out)
diff --git a/prace/pyproject.toml b/prace/pyproject.toml
index 68a311b16d5cb5269308e911149b2745f2c81c19..9d882d4dad8fa4e6f7a4a5cc4b18f78adb4cf468 100644
--- a/prace/pyproject.toml
+++ b/prace/pyproject.toml
@@ -22,6 +22,7 @@ dependencies = [
   "kaleido",
   "mathjax",
   "scipy",
+  "scikit-learn",
 ]
 
 [project.urls]