import%20marimo%0A%0A__generated_with%20%3D%20%220.25.2%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%20A%20filtration%20on%20your%20own%20simplices%20with%20%60phlite%60%0A%0A%20%20%20%20%20%20%20%20The%20Vietoris%E2%80%93Rips%20complex%20of%20%5C(n%5C)%20points%20has%20%5C(%5Cbinom%7Bn%7D%7B3%7D%5C)%20triangles.%20In%20the%20plane%20the%20**Delaunay%0A%20%20%20%20%20%20%20%20triangulation**%20has%20only%20about%20%5C(2n%5C)%2C%20and%20filtering%20it%20by%20edge%20length%20still%20finds%20the%20loops%20of%20a%20point%20cloud%0A%20%20%20%20%20%20%20%20(this%20is%20close%20to%20the%20alpha%20complex).%20We%20build%20it%20with%20scipy%2C%20hand%20the%20simplices%20to%20%60phlite%60%20as%20tuples%2C%20and%0A%20%20%20%20%20%20%20%20draw%20the%20representative%20loops%20on%20the%20points.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20itertools%0A%0A%20%20%20%20import%20altair%20as%20alt%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20phlite%0A%20%20%20%20import%20polars%20as%20pl%0A%20%20%20%20from%20scipy.spatial%20import%20Delaunay%0A%0A%20%20%20%20return%20Delaunay%2C%20alt%2C%20itertools%2C%20np%2C%20phlite%2C%20pl%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20pl)%3A%0A%20%20%20%20%23%20Points%20on%20two%20noisy%20circles%20of%20different%20sizes%0A%20%20%20%20rng%20%3D%20np.random.default_rng(1)%0A%0A%20%20%20%20def%20circle(centre%2C%20radius%2C%20n)%3A%0A%20%20%20%20%20%20%20%20angles%20%3D%20rng.uniform(0%2C%202%20*%20np.pi%2C%20n)%0A%20%20%20%20%20%20%20%20return%20np.c_%5Bnp.cos(angles)%2C%20np.sin(angles)%5D%20*%20radius%20%2B%20centre%20%2B%20rng.normal(scale%3D0.08%2C%20size%3D(n%2C%202))%0A%0A%20%20%20%20points%20%3D%20np.vstack(%5Bcircle((0%2C%200)%2C%201.0%2C%20150)%2C%20circle((2.6%2C%200.3)%2C%200.6%2C%2080)%5D)%0A%20%20%20%20point_frame%20%3D%20pl.DataFrame(%7B%22x%22%3A%20points%5B%3A%2C%200%5D%2C%20%22y%22%3A%20points%5B%3A%2C%201%5D%7D)%0A%20%20%20%20return%20point_frame%2C%20points%0A%0A%0A%40app.cell%0Adef%20_(Delaunay%2C%20itertools%2C%20np%2C%20pl%2C%20points)%3A%0A%20%20%20%20triangles%20%3D%20%5Btuple(sorted(t))%20for%20t%20in%20Delaunay(points).simplices.tolist()%5D%0A%20%20%20%20edges%20%3D%20sorted(%7Be%20for%20t%20in%20triangles%20for%20e%20in%20itertools.combinations(t%2C%202)%7D)%0A%20%20%20%20simplices%20%3D%20%5B(v%2C)%20for%20v%20in%20range(len(points))%5D%20%2B%20edges%20%2B%20triangles%0A%20%20%20%20edge_frame%20%3D%20pl.DataFrame(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22x%22%3A%20points%5B%5Ba%20for%20a%2C%20_%20in%20edges%5D%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22y%22%3A%20points%5B%5Ba%20for%20a%2C%20_%20in%20edges%5D%2C%201%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22x2%22%3A%20points%5B%5Bb%20for%20_%2C%20b%20in%20edges%5D%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22y2%22%3A%20points%5B%5Bb%20for%20_%2C%20b%20in%20edges%5D%2C%201%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22length%22%3A%20%5Bfloat(np.linalg.norm(points%5Ba%5D%20-%20points%5Bb%5D))%20for%20a%2C%20b%20in%20edges%5D%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%20%20%20%20pl.DataFrame(%7B%22points%22%3A%20%5Blen(points)%5D%2C%20%22edges%22%3A%20%5Blen(edges)%5D%2C%20%22triangles%22%3A%20%5Blen(triangles)%5D%7D)%0A%20%20%20%20return%20edge_frame%2C%20edges%2C%20simplices%2C%20triangles%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20The%20chain%20complex%20and%20its%20filtration%0A%0A%20%20%20%20Simplices%20are%20tuples%20of%20vertices%3B%20their%20boundary%20is%20the%20usual%20alternating%20sum%2C%20which%20we%20give%20as%20a%20mapping%20from%0A%20%20%20%20faces%20to%20coefficients%20(here%20over%20%5C(%5Cmathbb%7BZ%7D%2F3%5C)%2C%20to%20show%20that%20coefficients%20other%20than%20%5C(%5Cpm%201%5C)%20work%20too).%0A%20%20%20%20Edges%20get%20their%20length%3B%20vertices%20appear%20at%200%2C%20and%20triangles%20with%20their%20longest%20edge%20(%60extend%3DTrue%60).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(edge_frame%2C%20edges%2C%20phlite%2C%20pl%2C%20points%2C%20simplices)%3A%0A%20%20%20%20def%20boundary(simplex)%3A%0A%20%20%20%20%20%20%20%20if%20len(simplex)%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%7B%7D%0A%20%20%20%20%20%20%20%20return%20%7Bsimplex%5B%3Ai%5D%20%2B%20simplex%5Bi%20%2B%201%20%3A%5D%3A%20(-1)%20**%20i%20for%20i%20in%20range(len(simplex))%7D%0A%0A%20%20%20%20chain%20%3D%20phlite.ChainComplex.from_cells(simplices%2C%20dimension%3Dlambda%20s%3A%20len(s)%20-%201%2C%20boundary%3Dboundary%2C%20modulus%3D3)%0A%20%20%20%20values%20%3D%20%7B(v%2C)%3A%200.0%20for%20v%20in%20range(len(points))%7D%0A%20%20%20%20values.update(zip(edges%2C%20edge_frame%5B%22length%22%5D.to_list()))%0A%20%20%20%20barcode%20%3D%20chain.filtration(values%2C%20extend%3DTrue).persistence(1%2C%20representatives%3DTrue)%0A%20%20%20%20%23%20Essential%20bars%20never%20die%3A%20their%20death%20is%20null%20(charts%20need%20finite%20numbers)%0A%20%20%20%20bars%20%3D%20pl.DataFrame(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%22dimension%22%3A%20f%22H%7Bb.dimension%7D%22%2C%20%22birth%22%3A%20b.birth_value%2C%20%22death%22%3A%20None%20if%20b.is_essential%20else%20b.death_value%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20b%20in%20barcode.bars%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20schema%3D%7B%22dimension%22%3A%20pl.String%2C%20%22birth%22%3A%20pl.Float64%2C%20%22death%22%3A%20pl.Float64%7D%2C%0A%20%20%20%20)%0A%20%20%20%20barcode%0A%20%20%20%20return%20barcode%2C%20bars%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20bars%2C%20pl)%3A%0A%20%20%20%20_finite%20%3D%20bars.filter(pl.col(%22death%22).is_not_null())%0A%20%20%20%20_top%20%3D%20_finite%5B%22death%22%5D.max()%0A%20%20%20%20_diagonal%20%3D%20alt.Chart(pl.DataFrame(%7B%22t%22%3A%20%5B0.0%2C%20_top%5D%7D)).mark_line(color%3D%22grey%22%2C%20strokeWidth%3D1).encode(%0A%20%20%20%20%20%20%20%20x%3D%22t%3AQ%22%2C%20y%3D%22t%3AQ%22%0A%20%20%20%20)%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20_diagonal%0A%20%20%20%20%20%20%20%20%2B%20alt.Chart(_finite)%0A%20%20%20%20%20%20%20%20.mark_circle(size%3D40)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3Dalt.X(%22birth%3AQ%22%2C%20title%3D%22birth%20(edge%20length)%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3Dalt.Y(%22death%3AQ%22%2C%20title%3D%22death%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3Dalt.Color(%22dimension%3AN%22%2C%20title%3DNone)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3D%5B%22dimension%22%2C%20%22birth%22%2C%20%22death%22%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20).properties(width%3D320%2C%20height%3D320%2C%20title%3D%22persistence%20diagram%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Two%20H1%20points%20stand%20far%20from%20the%20diagonal%3A%20the%20two%20circles.%20Here%20are%20the%20edges%20of%20the%20complex%20at%20a%20scale%20where%0A%20%20%20%20both%20loops%20are%20present%2C%20with%20their%20representatives%20(lists%20of%20%60(edge%2C%20coefficient)%60)%20drawn%20on%20top.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20barcode%2C%20edge_frame%2C%20pl%2C%20point_frame%2C%20points)%3A%0A%20%20%20%20loops%20%3D%20sorted((b%20for%20b%20in%20barcode.bars%20if%20b.dimension%20%3D%3D%201)%2C%20key%3Dlambda%20b%3A%20b.persistence%2C%20reverse%3DTrue)%5B%3A2%5D%0A%20%20%20%20scale%20%3D%20max(b.birth_value%20for%20b%20in%20loops)%20%2B%200.01%0A%20%20%20%20_segments%20%3D%20pl.DataFrame(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22loop%22%3A%20f%22%5B%7B_b.birth_value%3A.2f%7D%2C%20%7B_b.death_value%3A.2f%7D)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22x%22%3A%20points%5B_e%5B0%5D%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y%22%3A%20points%5B_e%5B0%5D%2C%201%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22x2%22%3A%20points%5B_e%5B1%5D%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y2%22%3A%20points%5B_e%5B1%5D%2C%201%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_b%20in%20loops%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_e%2C%20_coefficient%20in%20_b.representative%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20_axis%20%3D%20dict(axis%3DNone%2C%20scale%3Dalt.Scale(zero%3DFalse))%0A%20%20%20%20_complex%20%3D%20alt.Chart(edge_frame.filter(pl.col(%22length%22)%20%3C%3D%20scale)).mark_rule(color%3D%22%23bbbbbb%22).encode(%0A%20%20%20%20%20%20%20%20x%3Dalt.X(%22x%3AQ%22%2C%20**_axis)%2C%20y%3Dalt.Y(%22y%3AQ%22%2C%20**_axis)%2C%20x2%3D%22x2%3AQ%22%2C%20y2%3D%22y2%3AQ%22%0A%20%20%20%20)%0A%20%20%20%20_loops%20%3D%20alt.Chart(_segments).mark_rule(strokeWidth%3D3).encode(%0A%20%20%20%20%20%20%20%20x%3D%22x%3AQ%22%2C%20y%3D%22y%3AQ%22%2C%20x2%3D%22x2%3AQ%22%2C%20y2%3D%22y2%3AQ%22%2C%20color%3Dalt.Color(%22loop%3AN%22%2C%20title%3D%22H1%20bar%22)%0A%20%20%20%20)%0A%20%20%20%20_points%20%3D%20alt.Chart(point_frame).mark_circle(size%3D12%2C%20color%3D%22black%22).encode(x%3D%22x%3AQ%22%2C%20y%3D%22y%3AQ%22)%0A%20%20%20%20(_complex%20%2B%20_loops%20%2B%20_points).properties(%0A%20%20%20%20%20%20%20%20width%3D560%2C%20height%3D300%2C%20title%3Df%22edges%20of%20length%20at%20most%20%7Bscale%3A.2f%7D%2C%20and%20the%20two%20longest%20H1%20representatives%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Watching%20the%20filtration%0A%0A%20%20%20%20At%20scale%20%5C(s%5C)%20the%20complex%20has%20every%20edge%20of%20length%20at%20most%20%5C(s%5C)%2C%20and%20every%20triangle%20whose%20edges%20are%20all%0A%20%20%20%20there.%20Components%20merge%20as%20edges%20appear%3B%20each%20circle%20closes%20into%20a%20loop%2C%20which%20is%20filled%20in%20once%20its%20triangles%0A%20%20%20%20span%20the%20middle.%20On%20the%20right%2C%20the%20bars%20alive%20at%20%5C(s%5C)%20are%20highlighted.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(bars%2C%20edge_frame%2C%20mo%2C%20np%2C%20pl%2C%20points%2C%20triangles)%3A%0A%20%20%20%20import%20base64%0A%20%20%20%20import%20tempfile%0A%0A%20%20%20%20import%20matplotlib%0A%0A%20%20%20%20matplotlib.use(%22Agg%22)%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20from%20matplotlib.animation%20import%20FuncAnimation%2C%20PillowWriter%0A%20%20%20%20from%20matplotlib.collections%20import%20LineCollection%2C%20PolyCollection%0A%0A%20%20%20%20def%20animate(draw%2C%20times%2C%20figsize%3D(10%2C%203.8)%2C%20fps%3D6)%3A%0A%20%20%20%20%20%20%20%20%22%22%22An%20animated%20GIF%20of%20%60draw(axes%2C%20t)%60%20over%20%60times%60%2C%20as%20an%20image%20to%20display.%22%22%22%0A%20%20%20%20%20%20%20%20fig%2C%20axes%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3Dfigsize%2C%20gridspec_kw%3D%7B%22width_ratios%22%3A%20%5B1.6%2C%201%5D%7D)%0A%20%20%20%20%20%20%20%20animation%20%3D%20FuncAnimation(fig%2C%20lambda%20t%3A%20draw(axes%2C%20t)%2C%20frames%3Dtimes)%0A%20%20%20%20%20%20%20%20with%20tempfile.NamedTemporaryFile(suffix%3D%22.gif%22)%20as%20file%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20animation.save(file.name%2C%20writer%3DPillowWriter(fps%3Dfps)%2C%20dpi%3D72)%0A%20%20%20%20%20%20%20%20%20%20%20%20plt.close(fig)%0A%20%20%20%20%20%20%20%20%20%20%20%20data%20%3D%20base64.b64encode(open(file.name%2C%20%22rb%22).read()).decode()%0A%20%20%20%20%20%20%20%20return%20mo.Html(f'%3Cimg%20src%3D%22data%3Aimage%2Fgif%3Bbase64%2C%7Bdata%7D%22%20style%3D%22max-width%3A%20100%25%22%20%2F%3E')%0A%0A%20%20%20%20def%20draw_barcode(ax%2C%20frame%2C%20t%2C%20colours%2C%20top)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Bars%20as%20horizontal%20lines%2C%20those%20alive%20at%20%60t%60%20in%20colour%2C%20the%20rest%20faded%2C%20and%20a%20line%20at%20%60t%60.%22%22%22%0A%20%20%20%20%20%20%20%20ax.clear()%0A%20%20%20%20%20%20%20%20for%20y%2C%20(dimension%2C%20birth%2C%20death)%20in%20enumerate(frame.iter_rows())%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20end%20%3D%20top%20if%20death%20is%20None%20else%20death%0A%20%20%20%20%20%20%20%20%20%20%20%20alive%20%3D%20birth%20%3C%3D%20t%20and%20(death%20is%20None%20or%20t%20%3C%20death)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.plot(%5Bbirth%2C%20end%5D%2C%20%5By%2C%20y%5D%2C%20color%3Dcolours%5Bdimension%5D%2C%20lw%3D2.2%2C%20alpha%3D1.0%20if%20alive%20else%200.15)%0A%20%20%20%20%20%20%20%20ax.axvline(t%2C%20color%3D%22black%22%2C%20lw%3D1)%0A%20%20%20%20%20%20%20%20ax.set_yticks(%5B%5D)%0A%20%20%20%20%20%20%20%20ax.set_xlim(0%2C%20top)%0A%20%20%20%20%20%20%20%20ax.set_title(f%22barcode%2C%20s%20%3D%20%7Bt%3A.2f%7D%22)%0A%0A%20%20%20%20_lengths%20%3D%20edge_frame%5B%22length%22%5D.to_numpy()%0A%20%20%20%20_segments%20%3D%20np.stack(%5Bedge_frame.select(%22x%22%2C%20%22y%22).to_numpy()%2C%20edge_frame.select(%22x2%22%2C%20%22y2%22).to_numpy()%5D%2C%20axis%3D1)%0A%20%20%20%20_triangle_values%20%3D%20np.array(%0A%20%20%20%20%20%20%20%20%5Bmax(np.linalg.norm(points%5Ba%5D%20-%20points%5Bb%5D)%20for%20a%2C%20b%20in%20%5B(t%5B0%5D%2C%20t%5B1%5D)%2C%20(t%5B1%5D%2C%20t%5B2%5D)%2C%20(t%5B0%5D%2C%20t%5B2%5D)%5D)%20for%20t%20in%20triangles%5D%0A%20%20%20%20)%0A%20%20%20%20_shown%20%3D%20bars.filter(pl.col(%22death%22).is_null()%20%7C%20((pl.col(%22death%22)%20-%20pl.col(%22birth%22))%20%3E%200.05)).sort(%0A%20%20%20%20%20%20%20%20%22dimension%22%2C%20%22birth%22%0A%20%20%20%20)%0A%20%20%20%20_top%20%3D%20float(_shown%5B%22death%22%5D.max())%20*%201.1%0A%0A%20%20%20%20def%20_draw(axes%2C%20t)%3A%0A%20%20%20%20%20%20%20%20ax%20%3D%20axes%5B0%5D%0A%20%20%20%20%20%20%20%20ax.clear()%0A%20%20%20%20%20%20%20%20ax.add_collection(%0A%20%20%20%20%20%20%20%20%20%20%20%20PolyCollection(%5Bpoints%5Blist(tri)%5D%20for%20tri%2C%20v%20in%20zip(triangles%2C%20_triangle_values)%20if%20v%20%3C%3D%20t%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20facecolor%3D%22tab%3Agreen%22%2C%20alpha%3D0.25)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20ax.add_collection(LineCollection(_segments%5B_lengths%20%3C%3D%20t%5D%2C%20color%3D%22%23555555%22%2C%20lw%3D0.6))%0A%20%20%20%20%20%20%20%20ax.scatter(points%5B%3A%2C%200%5D%2C%20points%5B%3A%2C%201%5D%2C%20s%3D5%2C%20color%3D%22black%22%2C%20zorder%3D3)%0A%20%20%20%20%20%20%20%20ax.set_xlim(points%5B%3A%2C%200%5D.min()%20-%200.2%2C%20points%5B%3A%2C%200%5D.max()%20%2B%200.2)%0A%20%20%20%20%20%20%20%20ax.set_ylim(points%5B%3A%2C%201%5D.min()%20-%200.2%2C%20points%5B%3A%2C%201%5D.max()%20%2B%200.2)%0A%20%20%20%20%20%20%20%20ax.set_aspect(%22equal%22)%0A%20%20%20%20%20%20%20%20ax.set_axis_off()%0A%20%20%20%20%20%20%20%20ax.set_title(%22the%20complex%20at%20scale%20s%22)%0A%20%20%20%20%20%20%20%20draw_barcode(axes%5B1%5D%2C%20_shown.select(%22dimension%22%2C%20%22birth%22%2C%20%22death%22)%2C%20t%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%22H0%22%3A%20%22tab%3Ablue%22%2C%20%22H1%22%3A%20%22tab%3Aorange%22%7D%2C%20_top)%0A%0A%20%20%20%20animate(_draw%2C%20np.linspace(0%2C%20_top%2C%2044))%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
c47a40c9abb8a78b467f637b9ca5a1e6